C. Gifted Composer
传送门
题意
一个字符串初始为空,每次操作可以在其开头或末尾添加一个字符,对每次操作后的字符串,询问有多少个不同的循环节长度?
思路
首先,对于循环节的判断,我们可以用哈希,有这样一个判定方法:
如果一个长度为 n n n 的字符串 s s s 有某个循环节长度 x x x,那么意味着: ∀ i > x , s i = s i − x \forall i > x, s_i = s_{i - x} ∀i>x,si=si−x
因此等价于: h a s h [ 1 , n − x ] = h a s h [ 1 + x , n ] hash[1, n - x] = hash[1 + x, n] hash[1,n−x]=hash[1+x,n],也就是 [ 1 , n − x ] [1, n - x] [1,n−x] 的哈希值与 [ 1 + x , n ] [1 + x, n] [1+x,n] 相等
同时,对于某个循环节长度 x x x,我们不难注意到它是满足二分单调性的,具体如下:
- 如果一个长度为 l e n len len 的字符串拥有循环节长度 x x x,那么在它的开头和末尾删除若干个字符,只要最终长度大于等于 x x x,它还是拥有循环节长度 x x x
- 如果某个长度大于等于 x x x 的字符串没有循环节长度 x x x,那么在它的末尾或开头添加若干个字符,永远也不会拥有循环节长度 x x x,这是因为在初始的 s s s 中已经有某个位置 s i ≠ s i − x s_i \neq s_{i - x} si=si−x,造成了污染
因此我们可以考虑对于每个循环节长度 x x x,我们通过二分来判断它最后出现的位置
预处理出最终字符串的 h a s h hash hash 数组,并记录每次操作后的头尾指针,然后利用差分计数即可
时间复杂度: O ( n log n ) O(n \log n) O(nlogn)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;const int INF=0x3f3f3f3f;
const long long INFLL=1e18;typedef long long ll;const int N = 1000050;std::pair<int, int> p[N]; //每次操作完后的头尾下标
ull s[3 * N];
ull hash[3 * N];
ull P[N]; //进制的幂inline ull f(std::string& c){if(c == "do") return 1;if(c == "re") return 2;if(c == "mi") return 3;if(c == "fa") return 4;if(c == "sol") return 5;if(c == "la") return 6;return 7;
}void init(int n){auto [l, r] = p[n];const ull p = 1313;ull h = 0;P[0] = 1;int cnt = 0;fore(i, l, r + 1){hash[i] = hash[i - 1] * p + s[i];++cnt;P[cnt] = P[cnt - 1] * p;}
}ull get_hash(int l, int r){return hash[r] - hash[l - 1] * P[r - l + 1];
}int main(){std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout.tie(nullptr);int n;std::cin >> n;int head = N, tail = N - 1;fore(i, 1, n + 1){char opt;std::string c;std::cin >> opt >> c;if(opt == 'a') s[++tail] = f(c);else s[--head] = f(c);p[i] = {head, tail};}init(n);std::vector<int> ans(n + 5, 0); //差分数组ans[n] = 1;fore(x, 1, n){int e = x;int l = x + 1, r = n;while(l <= r){int mid = l + r >> 1;auto [head, tail] = p[mid]; //mid步的左右端点ull v1 = get_hash(head, tail - x), v2 = get_hash(head + x, tail);if(v1 == v2){e = mid;l = mid + 1;}else r = mid - 1;}++ans[x];--ans[e + 1];}fore(i, 1, n + 1){ans[i] += ans[i - 1];std::cout << ans[i] << endl;}return 0;
}