题目链接
http://www.lydsy.com/JudgeOnline/problem.php?id=4377
= =
题意
给定n,a,b,p,其中n,a互质。定义一个长度为n的01串c[0..n-1],其中c[i]==0当且仅当(ai+b) mod n < p。
给定一个长为m的小01串,求出小串在大串中出现了几次。
思路
一开始自己想了想,只是看出了n,a互质保证了所有数只会出现一次(这个不知道为什么的自己看看相关数论把……),然后想了半天没想出来(太菜了QAQ),看了网上的题解也不是特别懂,后来问了gtw,他一说我就明白了,果然是数论大神 %%%
和网上其他题解一样,如果知道了一个位置的数字,那么它前后位置的数字都可以推出,又根据m串给出的大小关系(0,1代表了题中不等输的小于和大于等于),我们可以确定满足条件起点的取值范围(记得去掉后面几个长度没有m的子串),求出答案。
同时考虑,如果求答案可能存在的范围,就要对所有集合求交集,复杂度太高,于是我们求答案不可能存在的范围,对所有范围求并集。
对应的不等式:
0 -> [p - now, n - 1 - now] mod n
1 -> [0 - now, p - 1 - now] mod n
now是a的不断累加结果,因为我们要确定第一个数的取值,所以我们把当前的范围减一个now。
又根据模域的性质,如果l > r的话,就取[0, r]和[l, n - 1]。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int, int> P;
const int MAXN = 1e6 + 5;
int n, a, b, p, m;
char str[MAXN];
P range[MAXN * 3];
int rancnt;
void addrange(int l, int r) {
if (l <= r) {
range[rancnt].first = l;
range[rancnt++].second = r;
} else {
range[rancnt].first = l;
range[rancnt++].second = n - 1;
range[rancnt].first = 0;
range[rancnt++].second = r;
}
}
int main() {
while (~scanf("%d%d%d%d%d%s", &n, &a, &b, &p, &m, str)) {
rancnt = 0;
for (int i = 0, now = 0; i < m; ++i, (now += a) %= n) {
if (str[i] == '0') addrange((p - now + n) % n, (n - 1 - now + n) % n);
else addrange((n - now) % n, (p - 1 - now + n) % n);
}
for (int i = 0, now = (b + n - a) % n; i < m; ++i, (now += n - a) %= n) {
addrange(now, now);
}
sort(range, range + rancnt);
int ans = 0, head = range[0].first, tail = range[0].second;
for (int i = 1; i < rancnt; ++i) {
if (tail < range[i].first) {
ans += tail - head + 1;
head = range[i].first;
tail = range[i].second;
} else {
tail = max(tail, range[i].second);
}
}
ans += tail - head + 1;
printf("%d\n", n - ans);
}
}