【LOJ6500】操作 题解(差分+贪心+哈希)
8月17日考试 T3
题目大意:给定一个$01$序列,每次可以选择一个长度为$k$的区间取反。给定$q$次询问,每次询问$[l,r]$至少需要多少次操作才能使所有数变为$0$。
考虑差分。因此下面所有讲述都是基于差分数组来讲的。
设$b[i]=a[i]\ xor\ a[i-1]$。如果让$[l,r]$内所有数都变为$0$,我们不妨加上前导$0$和后导$0$,这样我们的目标就变为让$b[l]$和$b[r+1]$变为$0$。
首先考虑无解的情况。考虑到一个位置$i$仅对与它在模$k$意义下同一剩余系的位置有影响,我们不妨对在同一剩余系中的$1$赋上一个较大的随机数,对于$[l,r]$中的$1$,我们将它们的哈希值异或起来,如果结果不为$0$则无解。因为在差分数组中让区间全变为$0$即为消去一对$1$,如果有奇数个$1$显然是无解的。
考虑怎么维护对答案的贡献。设$ans[i]$表示$[1,i]$对答案的贡献,$pre[i]$表示在$i$之前的与$i$在同一剩余系的位置对答案的贡献,$nxt[i]$表示$pre[i]$加上$i$对答案的贡献。
考虑$b[i]=1$且$i\ \mod\ k=j$的所有位置$p_1,p_2,\cdots,p_{2t-1},p_{2t}$,我们贪心每次选择两个相邻最近的$1$,然后消去。这样答案为$\frac{(p_2-p_1)+\cdots+(p_{2t}-p_{2t-1})}{k}$。我们可以想办法考虑维护这个式子的前缀和。显然从左到右扫的时候第奇数个$1$对答案有负的贡献,第偶数个$1$对答案有正的贡献。因此每新加入一个$1$都会导致奇偶性的改变,我们就把原来的答案取反然后再加上$i$。这样我们就能处理$pre$和$nxt$了。知道了这两个,$ans$也很好求了。于是我们可以$O(n)$预处理,$O(1)$查询。
然而上述解法中有一点要注意:如果$a[l]$或者$a[r]$为$1$,由于加入了前导和后导$0$,此时$l$上和$r+1$上的位置的差分变成了$1$,而我们要将其变为$0$,所以此时边界要进行特殊处理。具体来讲就是先考虑$[l+1,r]$的贡献,然后再单独考虑$l$和$r+1$的贡献。
这样我们我们就在$O(n+m)$的时间内解决了这个问题。
代码:
#include <bits/stdc++.h> using namespace std; const int maxn = 2000005; int rd[maxn], ans[maxn], pre[maxn], nxt[maxn]; int a[maxn], b[maxn], hs[maxn], n, m, l, r, k; char ch[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } int main() { srand(time(0)); n = read(); k = read(); m = read(); for (int i = 0; i < k; i++) rd[i] = rand() * rand() + rand(); scanf("%s", ch + 1); for (int i = 1; i <= n; i++) { a[i] = ch[i] - '0'; b[i] = a[i] ^ a[i - 1]; } b[n + 1] = a[n + 1] ^ a[n]; for (int i = 1; i <= n + 1; i++) { hs[i] = hs[i - 1]; pre[i] = (i - k <= 0) ? 0 : nxt[i - k]; if (b[i]) nxt[i] = i - pre[i], hs[i] ^= rd[i % k]; else nxt[i] = pre[i]; ans[i] = ans[i - 1] + nxt[i] - pre[i]; } while (m--) { l = read(); r = read(); // b:l-r+1 int ret = ans[r] - ans[l], flag = hs[r] ^ hs[l]; // l+1-r if (a[r] == 1) flag ^= rd[(r + 1) % k], ret += (r + 1) - 2 * pre[r + 1]; // change r+1 from 1 to 0 if (a[l] == 1) flag ^= rd[l % k], ret += nxt[l] - (l - nxt[l]); // change l from 1 to 0 if (flag != 0) printf("-1\n"); else printf("%d\n", ret / k); } return 0; }