CF1398F
Solution
我又来贡献暴力做法了。。。听说两只log不可能过1e6?
有一个显然的想法是:
我们先预处理一个aia_iai表示第iii个位置的最长后缀长度,满足该后缀中不同时存在0和1。
假设我们要求x=ix=ix=i的答案,那么一个位置jjj可以作为一轮的结束点当且仅当aj≥ia_j\geq iaj≥i,而一个结束位置序列p1<p2<...<pkp_1<p_2<...<p_kp1<p2<...<pk合法当且仅当每一个pjp_jpj都是合法结束点并且相邻两个元素的差至少为iii(保证不重合)。
因此我们会贪心地从第一个满足aj≥ia_j\geq iaj≥i的jjj开始,每次找一个最小的j′j'j′,满足j′≥j+ij'\geq j+ij′≥j+i且aj′≥ia_{j'}\geq iaj′≥i,然后从jjj跳到j′j'j′,最终的答案就是跳的步数。
这显然可以直接对aia_iai建区间线段树,维护区间内的maxmaxmax,线段树上二分求出j′j'j′。
这个方法的时间复杂度为O(∑ansilogn)O(\sum ans_i\;\log n)O(∑ansilogn),而ansi≤⌊ni⌋ans_i\leq \lfloor\frac{n}{i}\rflooransi≤⌊in⌋,因此总时间复杂度O(nlnnlogn)O(n\ln n\log n)O(nlnnlogn),注意常数即可。
(PS:别用偷懒用set维护,常数起飞,实测接近线段树的三倍)。
Code
char st[MAXN];
int mx[MAXN << 2], a[MAXN];
void build(int x, int l, int r) {if (l == r) { mx[x] = a[l]; return; }int mid = (l + r) >> 1;build(x << 1, l, mid);build(x << 1 | 1, mid + 1, r);mx[x] = max(mx[x << 1], mx[x << 1 | 1]);
}
int query(int x, int l, int r, int L, int y) {if (mx[x] < y) return -1;if (l == r) return l;int mid = (l + r) >> 1;if (L > mid) return query(x << 1 | 1, mid + 1, r, L, y);else {int t = query(x << 1, l, mid, L, y);if (t != -1) return t;return query(x << 1 | 1, mid + 1, r, mid + 1, y);}
}
signed main() {
#ifndef ONLINE_JUDGEfreopen("a.in", "r", stdin);
#endifint n, cnt0 = 0, cnt1 = 0;read(n), reads(st);for (int i = 1, l = 1; i <= n ; ++ i) {auto add = [&](char x, int y) {if (x == '0') cnt0 += y; if (x == '1') cnt1 += y;};add(st[i], 1);while (cnt0 && cnt1) add(st[l ++], -1);a[i] = i - l + 1;}build(1, 1, n);for (int i = 1; i <= n ; ++ i) {int cnt = 0, r = 1;for (; r <= n ;) {r = query(1, 1, n, r, i);if (r == -1) break;r += i;++ cnt;}print(cnt), putc(' ');}return 0;
}
Update 4.5 23:30
上面的代码跑了1890ms1890ms1890ms,我们觉得它不够优秀,于是冷静分析一下它慢在哪里,发现全1的数据就可以把它卡满,这是为什么呢?这是因为我们在长长的没有0的段里面做了很多次query,这其实是不必要的,我们同样可以预处理以每个位置开始的最长段。这样就可以O(1)O(1)O(1)计算整个段的贡献。
我们提交后发现它只跑了249ms249ms249ms!
理性分析一下它的时间复杂度,相当于整个序列被01分割为若干段,跳到一个段就可以O(1)O(1)O(1)计算答案并且O(logn)O(\log n)O(logn)跳到下一个段,这样每一段会有O(len)O(len)O(len)次贡献(因为每一个长度大于iii的段才可能产生贡献),每次贡献为O(logn)O(\log n)O(logn),因此总时间复杂度为O(nlogn)O(n\log n)O(nlogn)!
Updated code
//线段树部分同上
signed main() {
#ifndef ONLINE_JUDGEfreopen("a.in", "r", stdin);
#endifint n, cnt0 = 0, cnt1 = 0;read(n), reads(st);for (int i = 1, l = 1; i <= n ; ++ i) {auto add = [&](char x, int y) {if (x == '0') cnt0 += y; if (x == '1') cnt1 += y;};add(st[i], 1);while (cnt0 && cnt1) add(st[l ++], -1);a[i] = i - l + 1;}cnt0 = 0, cnt1 = 0;for (int i = n, r = n; i >= 1 ; -- i) {auto add = [&](char x, int y) {if (x == '0') cnt0 += y; if (x == '1') cnt1 += y;};add(st[i], 1);while (cnt0 && cnt1) add(st[r --], -1);b[i] = r - i;}build(1, 1, n);for (int i = 1; i <= n ; ++ i) {int cnt = 0, r = 1;for (; r <= n ;) {r = query(1, 1, n, r, i);if (r == -1) break;int p = b[r] / i;cnt += p + 1, r += i * (p + 1);}print(cnt), putc(' ');}return 0;
}