LOJ6500. 「雅礼集训 2018 Day2」操作(哈希+差分)

题目链接

https://loj.ac/problem/6500

题解

区间取反 \(01\) 串的经典套路是差分。我们令 \(b_i = a_i\ {\rm xor}\ a_{i - 1}\)\(\{a_i\}\) 表示原 \(01\) 序列),这样每一次对一个长度为 \(k\) 的区间取反只会使两个 \(b_i\) 发生变化,不妨设为 \(b_x, b_y(x < y)\),那么一定满足 \(y - x = k\)。而我们的目的就是使得差分后数组 \(b\) 里的所有 \(1\) 变为 \(0\)

为了方便,接下来的分析均只使用差分后的数组 \(b\)

首先很显然的是,模 \(k\) 后值不同的位置绝不会互相影响。因此我们可以将所有位置 \(i\) 按照模 \(k\) 的值分组,模 \(k\) 的值相同的位置属于同一组。那么对于每次询问,有解当且仅当 \(b\) 数组的每一组在区间 \([l, r + 1]\) (由于是差分数组,因此要考虑位置 \(r + 1\))内都恰好包含偶数个 \(1\)。奇偶性的判断通常用哈希完成,具体地,我们可以给同一组内所有满足 \(b_i = 1\) 的位置 \(i\) 赋一个相同的较大的随机值,不同的组赋不同的值,那么一段区间有解,一定满足区间内所有值的异或和为 \(0\)。这样,我们就能够在 \(O(1)\) 的时间内判断区间是否有解。

接下来考虑如何计算最小操作次数。对于任意的 \(j(0 \leq j < k)\),假设满足 \(b_i = 1\)\(i\ {\rm mod}\ k = j\) 的所有位置 \(i\) 从小到大分别为 \(p_1, p_2, \cdots, p_{2t - 1}, p_{2t}\),那么考虑贪心,我们肯定是从左至右依次选择最近的两个 \(p\) 消去,因此答案为 \(\frac{(p_2 - p_1) + (p_4 - p_3) + \cdots + (p_{2t} - p_{2t - 1})}{k}\)。我们可以设法维护这个式子的前缀和。在从左至右依次处理时,由于每一组内从右至左的第奇数个已处理的满足 \(b_i = 1\)\(i\) 有正的贡献,第偶数个已处理的满足 \(b_i = 1\)\(i\) 有负的贡献,而新的 \(i\) 加入会导致奇偶性改变,即会发生正负交替,因此每加入一个 \(b_i = 1\)\(i\) 时,我们直接将 \(i\ {\rm mod}\ k\) 的所有位置的贡献和取反再加上 \(i\),最后再添加到前缀和中即可。

最后注意由于每次询问 \([l, r]\) 时我们要强制使得 \(a_{l - 1} = a_{r + 1} = 0\),因此区间的边界需要特殊处理。

这样,我们就在 \(O(n + m)\) 的时间内解决了此题。

代码

#include<bits/stdc++.h>

using namespace std;

#define X first
#define Y second
#define mp make_pair
#define pb push_back
#define debug(...) fprintf(stderr, __VA_ARGS__)

typedef long long ll;
typedef long double ld;
typedef unsigned int uint;
typedef pair<int, int> pii;
typedef unsigned long long ull;

template<typename T> inline void read(T& x) {
  char c = getchar();
  bool f = false;
  for (x = 0; !isdigit(c); c = getchar()) {
    if (c == '-') {
      f = true;
    }
  }
  for (; isdigit(c); c = getchar()) {
    x = x * 10 + c - '0';
  }
  if (f) {
    x = -x;
  }
}

template<typename T, typename... U> inline void read(T& x, U&... y) {
  read(x), read(y...);
}

template<typename T> inline bool checkMax(T& a, const T& b) {
  return a < b ? a = b, true : false;
}

template<typename T> inline bool checkMin(T& a, const T& b) {
  return a > b ? a = b, true : false;
}

const int N = 2e6 + 10;

int n, k, m, s[N][3], v[N];

char str[N];

ull hash_val[N], pre[N];

inline ull get_random() {
  return (1ull * rand() << 30) + (1ull * rand() << 15) + rand();
}

int main() {
  read(n, k, m);
  scanf("%s", str + 1), str[0] = '0';
  for (register int i = 0; i < k; ++i) {
    hash_val[i] = get_random();
  }
  for (register int i = 1; i <= n; ++i) {
    if (str[i] ^ str[i - 1]) {
      pre[i] = pre[i - 1] ^ hash_val[i % k];
      s[i][0] = s[i - 1][0] + i - (v[i % k] << 1);
      v[i % k] = i - v[i % k];
    } else {
      pre[i] = pre[i - 1];
      s[i][0] = s[i - 1][0];
    }
    s[i][1] = v[i % k];
    s[i][2] = v[(i + 1) % k];
  }
  for (register int i = 1; i <= m; ++i) {
    int l, r; read(l, r);
    ull t = pre[r] ^ pre[l];
    int res = s[r][0] - s[l][0];
    if (str[l] == '1') {
      t ^= hash_val[l % k];
      res -= l - (s[l][1] << 1);
    }
    if (str[r] == '1') {
      t ^= hash_val[(r + 1) % k];
      res += r + 1 - (s[r][2] << 1);
    }
    printf("%d\n", !t ? res / k : -1);
  }
  return 0;
}

转载于:https://www.cnblogs.com/ImagineC/p/9801026.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值