bzoj3160: 万径人踪灭
题目

分析
一道FFT神题。话说题目还真是会强行玩梗啊
首先需要进行转化。
不连续是一个假的条件。因为你可以把连续的用manacher算出来。所以总的答案就是子序列答案-manacher
然后先把序列变成回文串的形式。比如串
S=aba
S
=
a
b
a
变成
S′=xaxbxax
S
′
=
x
a
x
b
x
a
x
(下标从零开始)
我们另
fi
f
i
表示以i为中心的的对称字符的个数,整个答案就是
∑i=12∗n+12fi−1
∑
i
=
1
2
∗
n
+
1
2
f
i
−
1
所以我们只需要求
fi
f
i
我们考虑一个对称字符对答案的贡献。
假设字符
S′a=S′b
S
a
′
=
S
b
′
,并且
a+b=2∗mid
a
+
b
=
2
∗
m
i
d
那么
S′a,S′b
S
a
′
,
S
b
′
会对
mid
m
i
d
的答案产生1的贡献。
这个时候我们考虑
a,b
a
,
b
在原串上的位置,那么有
posa=⌊a2⌋+1
p
o
s
a
=
⌊
a
2
⌋
+
1
,b同理
于是有
如果
posa+1+posb+1
p
o
s
a
+
1
+
p
o
s
b
+
1
并且
Sposa=Sposb
S
p
o
s
a
=
S
p
o
s
b
那么在
posa+posb+1
p
o
s
a
+
p
o
s
b
+
1
的位置上会产生1的贡献。
我们把它写成递推的形式
2∗f[a+b+2]=∑an∑bn[Sa=Sb]+1
2
∗
f
[
a
+
b
+
2
]
=
∑
a
n
∑
b
n
[
S
a
=
S
b
]
+
1
这已经近乎是一个标准卷积式,只需要将bool表达式转化,容易想到令
a=0,b=1
a
=
0
,
b
=
1
再令
a=1,b=0
a
=
1
,
b
=
0
,利用FFT统计答案即可得到
f
f
的值。
一个细节就是,注意卷积中a,b的答案会统计到a+b上,所以f值的位置是最终卷积系数下标减二。具体看代码。
代码
我有一份垃圾的代码,可惜这里空间太大,必须放下
FFT打爆,manacher打爆,各种爆。还是得多提升编程水平。思路。。。不存在的。。
/**************************************************************
Problem: 3160
User: 2014lvzelong
Language: C++
Result: Accepted
Time:1620 ms
Memory:23168 kb
****************************************************************/
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const double pi = acos(-1);
const int N = 400010, mod = 1e9 + 7;
int s[N], R[N], st[N], p[N], bin[N], f[N], n, m;
void readchar() {
char ch = getchar();
while(ch != 'a' && ch != 'b') ch = getchar();
for(;ch >= 'a' && ch <= 'b'; ch = getchar()) s[n++] = ch - 'a';
}
struct cp {
double r, i;
cp(double real = 0, double image = 0) : r(real), i(image) {}
cp operator * (cp a) {return cp(r * a.r - i * a.i, r * a.i + i * a.r);}
cp operator + (cp a) {return cp(r + a.r, i + a.i);}
cp operator - (cp a) {return cp(r - a.r, i - a.i);}
}a[N], b[N];
void FFT(cp *F, int f) {
for(int i = 0;i < m; ++i) if(i < R[i]) swap(F[i], F[R[i]]);
for(int i = 1;i < m; i <<= 1) {
cp wn(cos(pi / i), f * sin(pi / i));
for(int j = 0;j < m; j += (i << 1)) {
cp w(1, 0);
for(int l = 0;l < i; ++l , w = w * wn) {
cp x = F[j + l], y = w * F[j + l + i];
F[j + l] = x + y; F[j + l + i] = x - y;
}
}
}
}
int manacher() {
st[0] = 3; st[1] = 2; st[(n << 1) + 2] = 4;
for(int i = 0;i < n; ++i) st[i + 1 << 1] = s[i], st[i + 1 << 1 | 1] = 2;
int id = 0, tot = 0;
for(int i = 1;i < (n << 1) + 1; ++i) {
if(i < id + p[id]) p[i] = min(id + p[id] - i, p[(id << 1) - i]);
while(st[i - p[i]] == st[i + p[i]] && i + p[i]) ++p[i];
if(i + p[i] > id + p[id]) id = i;
tot = (tot + p[i] / 2) % mod;
}
return tot;
}
void work(int p) {
for(int i = 0;i < n; ++i) a[i] = cp(s[i] ^ p);
FFT(a, 1);
for(int i = 0;i < m; ++i) b[i] = b[i] + a[i] * a[i];
}
int main() {
readchar();
bin[0] = 1; for(int i = 1;i < N; ++i) bin[i] = (bin[i - 1] << 1) % mod;
int L; for(m = 1, L = 0; m <= n << 1; ++L, m <<= 1);
for(int i = 0;i < m; ++i) R[i] = (R[i >> 1] >> 1) | ((i & 1) << L - 1);
work(1); memset(a, 0, sizeof(a)); work(0);
FFT(b, -1);
long long ans = 0;
for(int i = 2;i < (n << 1) + 1; ++i) f[i] = (long long)(b[i - 2].r + 0.5) / m;
for(int i = 2;i < (n << 1) + 1; ++i) ans = (ans + bin[(f[i] + 1) >> 1] - 1) % mod;
printf("%lld\n", (ans + mod - manacher()) % mod);
return 0;
}
本文详细解析了BZOJ3160: 万径人踪灭这一问题,介绍了如何通过Manacher算法与快速傅立叶变换(FFT)相结合来解决该问题。文章提供了完整的代码实现,并解释了如何计算以每个字符为中心的对称字符个数。

396

被折叠的 条评论
为什么被折叠?



