【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;
}

 

posted @ 2020-08-22 17:11  我亦如此向往  阅读(286)  评论(0编辑  收藏  举报