看題
也是給他綁上 \(\rm{Subtask}\) 了, 這我騙雞毛分啊
感冒也是非常難受, 但是事已至此, 先讀題吧
題目背景好看愛看
\(\rm{T1}\)
圖論題, 好玩
\(\rm{T2}\)
大概也是不會做, 再說
\(\rm{T3}\)
難繃, 考慮高檔暴力
\(\rm{T4}\)
這個肯定是暴力都難打
今天也是 \(30 \rm{min} + 1 \rm{h} + 15 \rm{min} + 1 \rm{h} 15 \rm{min}\)
\(\rm{T1}\)
先分析題意,
給定一張無重邊, 無環的無向圖
求加若干邊之後, 圖中最長鏈能達到多長
首先發現最後的圖一定包含了全部的 \(n\) 個點, 因為如果不包含點 \(i\) , 那麼選上點 \(i\) 之後, 最長的鏈一定會變得更長
容易的, 我們發現原圖應該是一個森林, 那麼我們需要把原圖中的森林連線起來, 使得最長鏈最長, 符合直覺的, 我們應該去連線每顆樹的直徑
那麼好, 這題結束了
首先讀入是顯然的, 對於每一個連通塊, 兩遍 \(\rm{dfs}\) 求直徑的兩端
對於第 \(i\) 個連通塊, 我們考慮將 \(i - 1\) 連通塊的右側 (這裡的左右人為指定) , 和 \(i\) 的左側連線起來, 對於 \(i = 1/n\) 情況特殊處理
\(\rm{T2}\)
只過了 \(25 \rm{min}\) , 沖沖衝
\(\rm{T1}\) 多半是大眾分, 當然我多半拿不到大眾分就是了 (確實沒拿到)
轉化題意
對於序列 \(s\) , 找出一段區間 \([L, R]\) , 使得區間長度至少為 \(k\) 的前提下, 令所有數的 \(\gcd\) 為 \(g\) , 求 \(\displaystyle g \times \sum_{i = L}^{R} s_i \to \max\)
題意其實給的很清楚了, 不太需要轉化
區間長是 \(10^6\) 級別的, 不好處理
暴力的做法是, 考慮對於一個固定的 \(R\) , 我們向前走, 記錄 \(\sum\) 值和 \(\gcd\) , 可以做到 \(\mathcal{O} (n ^ 2)\) 的時間複雜度, \(30 \rm{pts}\)
我們還需要最佳化, 這裡最佳化的點很明確, 我們需要利用之前的計算結果, 不能再次重複計算
但是並不好利用之前的計算結果, 考慮神秘最佳化
注意到當右端點確定, \(\gcd\) 的趨勢是單調不增, 我們考慮預處理出所有 \(\gcd\) 的變化區間的最左側點, 只考慮最左端點即可, 期望上是 \(\mathcal{O} (n \sqrt{n})\) 的, 可以拿到 \(60 \rm{pts}\)
一會再回來想一想
對於右端點的每一次拓張, 我們向左列舉到第一個值為當前 \(s_i\) 因數的最左側點, 中間的全部都要並過來, 即在棧中彈出, 如果左邊沒有因數, 彈出完了之後插入一個 \(1\) , 特別的, 判斷一下 \(s_i\) 和當前棧頭的關係, 看 \(s_i\) 是否需要單開區間
這個可以拿棧處理
\(\rm{T3}\)
轉化題意
對於序列 \(s\) , 找出多少個區間 \([L, R]\) 滿足其中有一半以上的元素相同
對於 \(\rm{Subtask} 1, 2\) , \(\mathcal{O} (n ^ 2 \log n)\) 演算法可以解決
對於 \(\rm{Subtask} 3\) ,
我們先預處理出兩種數在區間中的出現次數的差出現次數的字首和, 然後就簡單了
程式碼
沒有好實現的, 按分值打
\(\rm{T1}\)
寫的差不多, 看看會不會掛
#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e5 + 20;
#define FILE_IO
int n, m;
class Graph_Class
{
private:
public:
/*並查集*/
struct DSU_struct
{
int fa[MAXN];
void fa_init() { for (int i = 1; i <= n; i++) fa[i] = i; }
int find(int x) {
return fa[x] = (x == fa[x]) ? x : find(fa[x]);
}
void merge(int u, int v) {
int fa_u = find(u), fa_v = find(v);
fa[fa_v] = fa_u;
}
} DSU;
struct node
{
int to;
int next;
} Edge[MAXN << 1];
int Edge_cnt = 0;
int head[MAXN];
void Edge_init() { for (int i = 1; i <= m * 2; i++) Edge[i].next = -1; }
void head_init() { for (int i = 1; i <= n; i++) head[i] = -1; }
void addedge(int u, int v) {
Edge[++Edge_cnt].to = v;
Edge[Edge_cnt].next = head[u];
head[u] = Edge_cnt;
}
} Graph;
class Sol_Class
{
private:
int dist[MAXN];
void dfs1(int Now, int fa, int d) {
dist[Now] = d;
for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next) {
int NowTo = Graph.Edge[i].to;
if (NowTo == fa) continue;
dfs1(NowTo, Now, d + 1);
}
}
int CalcD(int NowTree) {
for (int i = 1; i <= n; i++) {
if (Graph.DSU.find(i) == NowTree) {
dfs1(i, -1, 1);
break;
}
}
int Root = -1, maxdis = 0;
for (int i = 1; i <= n; i++) {
if (Graph.DSU.find(i) != NowTree) continue;
if (dist[i] > maxdis) maxdis = dist[i], Root = i;
}
memset(dist, 0, sizeof(dist));
dfs1(Root, -1, 1);
Root = -1, maxdis = 0;
for (int i = 1; i <= n; i++)
{
if (Graph.DSU.find(i) != NowTree) continue;
if (dist[i] > maxdis) maxdis = dist[i], Root = i;
}
return maxdis;
}
public:
bool Treevis[MAXN];
int Ans = 0;
/*分割森林, 計算直徑*/
void solve()
{
for (int i = 1; i <= n; i++) {
int NowTree = Graph.DSU.find(i);
if (Treevis[NowTree]) continue;
Ans += CalcD(NowTree);
Treevis[NowTree] = true;
}
printf("%lld", Ans);
}
} Sol;
signed main()
{
#ifdef FILE_IO
freopen("chariot.in", "r", stdin);
freopen("chariot.out", "w", stdout);
#endif
scanf("%lld %lld", &n, &m);
Graph.DSU.fa_init();
Graph.head_init();
Graph.Edge_init();
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%lld %lld", &u, &v);
Graph.addedge(u, v), Graph.addedge(v, u);
Graph.DSU.merge(u, v);
}
Sol.solve();
return 0;
}
\(\rm{T2}\)
稍微複雜, 看運氣了, 話說我今天 \(\rm{luogu}\) 都沒打卡
最佳化方法可能是錯的, 跑不過大樣例, 只能留在後面看看能騙多少
#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e6 + 20;
#define FILE_IO
int n, k;
int h[MAXN];
int Sum[MAXN];
class Subtask_3
{
private:
/*棧*/
struct node {
int Pos;
int Val;
} ;
struct Stack_struct
{
node Stack[MAXN];
int tp = 0;
bool empty() {
return tp == 0;
}
void pop() {
tp--;
}
void push(node x) {
Stack[++tp] = x;
}
node top() {
return Stack[tp];
}
} S;
public:
/*處理*/
void solve()
{
int Ans = 0;
for (int i = 1; i <= n; i++)
{
/*處理 gcd 左端點*/
while (!S.empty()) {
node Now = S.top();
if (h[i] % Now.Val == 0) break;
S.pop();
}
if (S.empty()) S.push({1, 1});
if (h[i] != S.top().Val) S.push({i, h[i]});
/*計算答案*/
for (int j = 1; j <= S.tp; j++) {
int NowAns = (Sum[i] - Sum[S.Stack[j].Pos - 1]) * S.Stack[j].Val;
if (i - S.Stack[j].Pos + 1 < k) continue;
Ans = std::max(Ans, NowAns);
}
}
printf("%lld", Ans);
}
} S_3;
class Subtask_12
{
private:
public:
void solve()
{
int Ans = 0;
for (int i = 1; i <= n; i++) {
int Sum = 0, Gcd = h[i];
for (int j = i; j >= 1; j--) {
Sum += h[j];
Gcd = std::__gcd(Gcd, h[j]);
if (i - j + 1 >= k) Ans = std::max(Ans, Sum * Gcd);
}
}
printf("%lld", Ans);
}
} S_12;
signed main()
{
#ifdef FILE_IO
freopen("intelligence.in", "r", stdin);
freopen("intelligence.out", "w", stdout);
#endif
scanf("%lld %lld", &n, &k);
for (int i = 1; i <= n; i++)
scanf("%lld", &h[i]), Sum[i] = Sum[i - 1] + h[i];
if (n > 10000) S_3.solve();
else S_12.solve();
return 0;
}
只能趕緊衝 \(\rm{T3}\) , 最佳化思路能騙 \(10 \rm{pts}\) 就非常好了
\(\rm{T3}\)
沒打完, 寄