Codeforces Round #626 (Div. 1)
Codeforces Round #626 (Div. 1)
A
略
B
對每一位分別計算,只考慮 \(x,y\) 的前 \(k\) 位,若 \(x+y\) 的第 \(k\) 位為 \(1\),則 \(x+y\in[2^k,2^{k+1})\cup[2^k+2^{k+1},2^{k+2})\),排序後雙指標統計即可
C
結論題,但大多數題解的證明有問題,建議看官方題解或[中文版題解](題解 CF1322C [Instant Noodles] - 老年退役選手 QwQcOrZ 的小窩 - 洛谷部落格 (luogu.com.cn)),享受優美嚴謹的證明
D
設 \(f_{i,j}\) 表示有 \(j\)
轉移 \(1\) 的時間複雜度顯然是 \(O(n^2)\),對於轉移 \(2\),每向上一層數量減半,複雜度為 \(O(n\times(n+\frac{n}{2}+\frac{n}{4}+\dots))=O(n^2)\)
int n, m, mx, res, l[N], s[N], c[N << 1], f[N << 1][N << 1]; void Max (int &a, const int b) { if (b > a) a = b; } void modify (int p, int x) { int tmp = 0; while (f[p][tmp] != INT_MIN) ++tmp; for (int i = tmp; i >= 1; --i) Max (f[p][i], f[p][i - 1] + c[p] - x); } void update (int p) { for (int i = 0; i <= n && f[p][i] != INT_MIN; ++i) Max (f[p + 1][i >> 1], f[p][i] + (i >> 1) * c[p + 1]); if (p < n + m) update (p + 1); } signed main() { read (n), read (m); for (int i = 1; i <= n; ++i) read (l[i]); for (int i = 1; i <= n; ++i) read (s[i]); for (int i = 1; i <= n + m; ++i) for (int j = 0; j <= n * 2; ++j) f[i][j] = INT_MIN; for (int i = 1; i <= n + m; ++i) read (c[i]), f[i][0] = 0; for (int i = n; i >= 1; --i) modify (l[i], s[i]), update (l[i]); for (int i = 1; i <= n + m + 1; ++i) Max (res, f[i][1]); return printf ("%lld\n", res), 0; }
E
暴力做法如何確定最後的序列?列舉一個值 \(k\),\(b_i=(a_i<k)\),也就是隻記錄 \(a_i\) 和 \(k\) 的大小關係。設 \(a',b'\) 為變換後最終得到的 \(a,b\)。取中位數的操作並不會改變這個大小關係。不難發現,一段長度 \(\ge2\) 的連續的 \(0/1\) 段永遠也不會變(稱它們為 \(0/1\) 段),相鄰的連續段之間夾了 \(010101\dots\) 這樣的東西,然後會從兩頭往中間擴散,這樣就得到了 \(b'\),算完所有 \(k\) 後也就可以得到 \(a'\),而操作次數就是所有 \(k\) 對應的 \(b\) 的最長的 \(01\) 段長度除以 \(2\)。\(k\) 取所有 \(a_i\),複雜度 \(O(n^2)\)
介紹兩種優化方法。
第一種:依舊從小到大列舉 \(k=a_i\),此時 \(b\) 中有一些 \(0\) 變為 \(1\),對於每一個變化的位置,\(b’\) 中也有一些 \(0\) 變為 \(1\),且構成一個區間,可用 \(set\) 等資料結構維護,\(O(n \log n)\)。但又長又慢
第二種:對單個位置 \(x\) 進行計算,因為只考慮一個數,所以可以二分 \(k\),不必列舉。剩下的問題是如何找到離 \(x\) 最近的連續段。令 \(p_i=min/max(a_i,a_{i+1})\),然後反著處理 \(p\) 的最大值最小值的 \(ST\) 表(對於 \(min(a_i,a_{i+1})\) 反過來處理 \(max\) 的 \(st\) 表 \(MX\),對於 \(max(a_i,a_{i+1})\) 處理 \(min\) 的 \(st\) 表 \(MN\)),再套個二分分別算出最近的 \(0\) 段和 \(1\) 段,即可得到最近的連續段,\(O(n \log^2 n)\)。但還有更強的做法。不用二分 \(k\),直接二分距離 \(d\)。設 \(A=MN.ask(i-d-1,i+d),B=MX.ask(i-d-1,i+d)\)。取 \(k=min(A,B)\),此時 \(B\ge k\),說明在距離 \(d\) 內已經碰到 \(0\) 段,\(A\ge k\),說明距離 \(d\) 內沒有 \(1\) 段,那麼先碰到 \(0\) 段,\(b'_i=(a'_i<k)=0\),即 \(a’_i\ge k\)。只需找到最大的 \(k=min(A,B)\) 就是 \(a'_i\) 的值。隨著 \(d\) 增大,\(A\) 單減,\(B\) 單增,二分交點即可求得最大值(函式不連續,不一定有真正的交點)。至於操作次數,也可以順帶著求出
int n, res, a[N], lg[N], ma[N], na[N], b[N];
struct st {
int c[20][N];
void work (int *a) {
memcpy (c[0], a, (n + 1) << 2);
for (int j = 1; j < 20; ++j)
for (int i = 0; i + (1 << j) - 1 <= n; ++i)
c[j][i] = min (c[j - 1][i], c[j - 1][i + (1 << (j - 1))]);
}
int ask (int x, int d) {
int l = x - d - 1, r = x + d, t = lg[r - l + 1];
return min (c[t][l], c[t][r - (1 << t) + 1]);
}
} mx, mn;
signed main() {
read (n); lg[0] = -1;
for (int i = 1; i <= n + 1; ++i) lg[i] = lg[i >> 1] + 1;
for (int i = 1; i <= n; ++i) read (a[i]);
a[0] = a[1], a[n + 1] = a[n];
for (int i = 0; i <= n; ++i)
ma[i] = -min (a[i], a[i + 1]), na[i] = max (a[i], a[i + 1]);
mn.work (na), mx.work (ma);
for (int i = 1; i <= n; ++i) {
int l = 0, r = min (i - 1, n - i), mid;
int ta, tb, val = 0;
while (l <= r) {
mid = l + r >> 1;
ta = mn.ask (i, mid), tb = -mx.ask (i, mid);
val = max (val, min (ta, tb));
ta > tb ? l = mid + 1 : r = mid - 1;
}
b[i] = val, res = max (res, r + 1);
}
printf ("%d\n", res);
for (int i = 1; i <= n; ++i) printf ("%d ", b[i]); puts ("");
return 0;
}
F
。。。。。