【题解】【NOIp2020 T2 P7114 字符串匹配】
首先考虑暴力:令
然后我们发现 诡异特殊,考虑从它来优化。结合上面的暴力分析,容易想到
所以,
最后我们发现时间复杂度的瓶颈在于找到最短的
此算法是
具体实现还有一(很)些(多)细节,参见代码
#include <cstdio>
#include <cstring>
#include <cctype>
typedef unsigned long long ULL;
const ULL MAXN = (1 << 20) + 5, BASE = 2017;
char s[MAXN]; // 读入的字符串
int t, len; // t为数据组数,len为字符串长度
ULL h[MAXN]; // h[i]表示 长度为i的前缀 的Hash值
int jcnt, djcnt[MAXN]; // jcnt表示F(T),djcnt[i]表示 F(S_i至S_n组成的字符串)
bool isj[30], disj[30]; // (i从0开始)isj[i]表示第i个字母出现奇数次是true/false,disj[i]意义与isj[i]相同,但为倒序更新
ULL ans; // 答案
//int cnt[30]; // cnt[i]表示有i个字母出现奇数次的T的真前缀的数量
int bit[30]; // 表示由cnt[i]构成的树状数组
inline int lowbit(int x)
{ return x & (-x); }
void update(int x) {
++x; //树状数组不能处理下标为0的情况,故“平移”一位
while(x <= 27) {
++bit[x];
x += lowbit(x);
}
}
int sum(int x) {
++x;
int s = 0;
while(x >= 1) {
s += bit[x];
x -= lowbit(x);
}
return s;
}
// 手写读入,返回读入字符串的长度
// (但好像并没有什么卵用
int input(char* s) {
char c = getchar();
int i = 0;
while(!islower(c))
c = getchar();
while(islower(c)) {
s[++i] = c;
c = getchar();
}
return i;
}
int main() {
// freopen("string.in", "r", stdin);
// freopen("string.out", "w", stdout);
scanf("%d", &t);
while(t--) {
len = input(s);
djcnt[len] = 1; // 最后一个字符重复了一次(奇数次)
disj[s[len] - 'a'] = 1; // 最后一个字符出现奇数次=true
for(int i = len - 1; i >= 1; --i) //预处理djcnt[i]以快速计算C
djcnt[i] = djcnt[i + 1] + ((disj[s[i] - 'a'] = !disj[s[i] - 'a']) == 1 ? 1 : -1);
h[1] = s[1] - 'a'; // 第1个字符的Hash值
for(int i = 2; i <= len; ++i) // 递推计算Hash值
h[i] = h[i - 1]*BASE + s[i] - 'a';
ULL a = BASE*BASE;
update(1); // 第一个字符重复了一次(奇数次)
isj[s[1] - 'a'] = 1; // 第一个字符出现奇数次=true
jcnt = 1; // 有一个字符出现奇数次
for(int i = 2; i < len; ++i) { // 循环从2开始(否则A,B中必有一空串)到len-1结束(否则C为空串)
int x = 1; // T的循环次数
for(int j = (i << 1); j <= len; j += i) {
/* e.g.
* h[i]: abc
* h[j - i]: abcabc
* h[j]: abcabc???
* h[j - i]*a: ---abcabc
* h[j] - h[j - i]*a: abc
* 容易发现只有???=abc才有h[j] - h[j - i]*a == h[i]
*/
if(h[j] - h[j - i]*a == h[i])
++x;
else
break;
}
// 后面四行是计算A,B,C的情况
if(x*i == len) //此时x*i+1=len+1,由于我没有清空djcnt所以不能把djcnt[len+1]视为0,故不能沿用else情况的代码
ans += 1ULL*bit[1]*((x - 1) >> 1); // C不能是空串,故比else的情况少一个C
else
ans += 1ULL*sum(djcnt[x*i + 1])*((x + 1) >> 1); // C_1,C_3,...,C_k(k为奇数且<=x)都可以
// 后面一行是计算A,B,ABC的情况
ans += 1ULL*sum(djcnt[(x - 1)*i + 1])*(x >> 1); // C_2,C_4,...,C_k(k为奇数且<=x)都可以
update((isj[s[i] - 'a'] = !isj[s[i] - 'a']) ? ++jcnt : --jcnt); // 注意这里要在计算后更新,否则B可能会为空串
a *= BASE; //T的长度增加,a也要随之增加
}
printf("%llu\n", ans);
memset(disj, 0, sizeof(disj));
memset(isj, 0, sizeof(isj));
memset(bit, 0, sizeof(bit));
ans = 0;
}
return 0;
}