BZOJ3160 万径人踪灭 字符串 多项式 Manachar FFT

原文链接http://www.cnblogs.com/zhouzhendong/p/8810140.html

题目传送门 - BZOJ3160

题意

  给你一个只含$a,b$的字符串,让你选择一个子序列,使得:

  $1.$位置和字符都关于某一条对称轴对称。

  $2.$不能是连续的一段。

  问原来的字符串中能找出多少个这样的子序列。答案对$10^9+7$取模。

  串长$\leq 10^5$。

题解

  下面的讨论都在满足条件$1$的情况下进行。

  首先,我们先不考虑条件$2$。然后再减掉不满足条件$2$的就可以了。

  显然不满足条件$2$的就是一个连续的回文串。我们只需要统计原串中的连续回文子串个数即可,这个可以用$Manachar$算法轻松搞定。

  考虑只满足条件$1$的。

  注意考虑第一个条件中位置也关于某一条对称轴对称。

  显然,考虑一个位置$i$,如果$s_{i-j}\neq s_{i+j}((i+j)\in N,(i-j)\in N)$,那么显然不能出现这一对字符。否则,这对字符就可以出现也可以不出现。

  如果位置$i$的两侧有$k$对可以出现也可以不出现的字符,那么对答案的贡献就是$2^k-1$。

  减$1$的原因是不能不取任何字符对,如果那样的话是空串,不满足条件。

  我们观察$s_{i-j}$和$s_{i+j}$的下标,发现$i-j+i+j=2i$考虑$j$为变量的话是个定值,那么恰好满足的多项式乘法卷积的形式。

  考虑构造多项式。

  下面引用框里面是我一开始自己想的算法,常数要大不少,可能过不去,但是也是对的。

设$f_i,g_i$分别表示的$i$位的字符值$(‘a‘=0,‘b‘=1)$和的$i$位是否有效$(下标超过原来的串长则g_i=0,否则g_i=1)$。

构造卷积

$$h_i=\sum_{j=0}^{i}g_ig_{i-j}(f_i-f_{i-j})^2$$

然后展开就可以$FFT$了。

得到的$h_i$是当以$\frac i2$为对称轴的时候$\frac i2$左右不能匹配的对数。

  但是事实上有更好的做法。

  考虑$‘a‘$和$‘b‘$,分开考虑,然后合法的数对个数加起来就是总的合法数对个数了。

  这里只说$‘a‘$。

  定义$g_i$,表示如果$s_i=‘a‘$则$g_i=1$,否则$g_i=0$。

  卷积$f=g^2$,即$f_i=\sum_{j=0}^i g_jg_{i-j}$。$FFT$优化即可。

  得到的$f_i$和之前的意义差不多,只是表示的是能匹配的对数了。

  最后计算答案不用说了吧QAQ。

  然而由于博主只写过$2$~$3$次$Manachar$,不够熟练,导致$Manachar$写错了,贡献了一次$TLE$,一次$WA$。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1<<18;
const LL mod=1e9+7;
double PI=acos(-1.0);
int m,n,L,R[N],r[N],res[N];
char s[N],str[N];
LL pow2[N],ans=0;
struct C{
	double r,i;
	C(){}
	C(double a,double b){r=a,i=b;}
	C operator + (C x){return C(r+x.r,i+x.i);}
	C operator - (C x){return C(r-x.r,i-x.i);}
	C operator * (C x){return C(r*x.r-i*x.i,r*x.i+i*x.r);}
}w[N],x[N],y[N],z[N];
void Manachar(char str[]){
	for (int i=0;i<m;i++)
		str[i*2+1]=s[i];
	for (int i=0;i<=m;i++)
		str[i*2]=‘*‘;
	int R=0,p=0;
	for (int i=1;i<m*2;i++){
		r[i]=max(1,min(r[p*2-i],R-i));
		while (i-r[i]>=0&&i+r[i]<=m*2&&str[i-r[i]]==str[i+r[i]])
			r[i]++;
		if (i+r[i]>R)
			R=i+r[i],p=i;
	}
}
void FFT(C a[]){
	for (int i=0;i<n;i++)
		if (i<R[i])
			swap(a[i],a[R[i]]);
	for (int t=n>>1,d=1;d<n;d<<=1,t>>=1)
		for (int i=0;i<n;i+=(d<<1))
			for (int j=0;j<d;j++){
				C tmp=w[t*j]*a[i+j+d];
				a[i+j+d]=a[i+j]-tmp;
				a[i+j]=a[i+j]+tmp;
			}
}
int main(){
	scanf("%s",s);
	m=strlen(s);
	Manachar(str);
	for (n=1,L=0;n<m*2;n<<=1,L++);
	for (int i=0;i<n;i++){
		R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
		w[i]=C(cos(2*i*PI/n),sin(2*i*PI/n));
		pow2[i]=i==0?1:(pow2[i-1]*2%mod);
		x[i]=C(0,0),y[i]=C(0,0);
	}
	for (int i=0;i<m;i++)
		x[i]=C(s[i]==‘a‘,0),y[i]=C(s[i]==‘b‘,0);
	FFT(x),FFT(y);
	for (int i=0;i<n;i++)
		x[i]=x[i]*x[i],y[i]=y[i]*y[i],w[i].i*=-1.0;
	FFT(x),FFT(y);
	for (int i=0;i<n;i++)
		res[i]=(int)((x[i].r+y[i].r)/n+0.5);
	for (int i=0;i<=m*2-2;i++)
		ans=(ans+mod+pow2[(res[i]+1)/2]-r[i+1]/2-1)%mod;
	printf("%lld",ans);
	return 0;
}

  

原文地址:https://www.cnblogs.com/zhouzhendong/p/8810140.html

时间: 2024-10-09 15:29:34

BZOJ3160 万径人踪灭 字符串 多项式 Manachar FFT的相关文章

多项式乘法(FFT)学习笔记

------------------------------------------本文只探讨多项式乘法(FFT)在信息学中的应用如有错误或不明欢迎指出或提问,在此不胜感激 多项式 1.系数表示法     一般应用最广泛的表示方式     用A(x)表示一个x-1次多项式,a[i]为$ x^i$的系数,则A(x)=$ \sum_0^{n-1}$ a[i] * $ x^i$ 仅利用这种方式求多项式乘法复杂度为O($ n^2$),不够优秀2.点值表示法     将n个互不相同的值$ x_0$...$

bzoj 3160: 万径人踪灭 manachar + FFT

3160: 万径人踪灭 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 133  Solved: 80[Submit][Status][Discuss] Description Input Output Sample Input Sample Output HINT 以每一个位置为中心,分别处理连续一块的回文串,回文序列个数. 比较容易看出是FFT+manachar,但是FFT还是不太熟悉,特别要注意三层for语句中i,j,k不能写错,这东西很难调

BZOJ3160 万径人踪灭 【fft + manacher】

题解 此题略神QAQ orz po神牛 由题我们知道我们要求出: 回文子序列数 - 连续回文子串数 我们记为ans1和ans2 ans2可以用马拉车轻松解出,这里就不赘述了 问题是ans1 我们设\(f[i]\)表示以i位置为中心的对称的字符对数,那么i位置产生的回文子序列数 = \(2^{f[i]} - 1\) 如何求? 由对称的性质,以i为对称中心的两点\(a,b\)满足\(a+b=2*i\) 我们可以设一个这样的序列: \(c[n]\)表示以\(n/2\)位置为对称点的对称点对数[n/2若

BZOJ3160万径人踪灭

题解: 题意即求不连续但间隔长度对称的回文串个数. 若si=sj,则这对字符可以作为以(i+j)/2为中心的回文串的一部分. 用F[i]来表示可以做为以i/2为中心的回文串的一部分的字符对数,则以i/2为中心的回文串数为2^F[i]. 则这就成了多项式乘法:先做一次a的,把字符为a的位置值赋为1,其余为0,进行一次FFT:同理做一次b的. 因为完全连续是不可以的,所以用Manacher求出这样的回文串的个数并减去. 代码: (BZOJ上PASCAL跑得不够快,再加上这题时限只有10s,并没有AC

[bzoj3160]万径人踪灭

Manacher+FFT,这题太精妙了,我现在才写是不是太弱了... Ans=以某个轴为中心的每一种回文子序列-所有连续回文串方案数 连续部分可以用Manacher在O(n)时间内解决. 第一部分:令f[i]=以i为中轴的对称对数,则对(2^f[i])-1求和即可(不能光有一根轴) 长串中i左右对称的对数位置相加一定是i(在短串中),那么f[i]=[sigma((str[x]==str[i-x])(x<i))+1]/2 这时题目还有个条件哟-串只是由a,b构成的,我们考虑一个卷积的形式C[k]=

洛谷.3803.[模板]多项式乘法(FFT)

题目链接:洛谷.LOJ. FFT相关:快速傅里叶变换(FFT)详解.FFT总结.从多项式乘法到快速傅里叶变换. #include <cmath> #include <cctype> #include <cstdio> #include <algorithm> #define gc() getchar() const int N=1e6+5; const double PI=acos(-1); int n,m; struct Complex { double

[uoj#34] [洛谷P3803] 多项式乘法(FFT)

新技能--FFT. 可在 \(O(nlogn)\) 时间内完成多项式在系数表达与点值表达之间的转换. 其中最关键的一点便为单位复数根,有神奇的折半性质. 多项式乘法(即为卷积)的常见形式: \[ C_n=\sum\limits_{i=0}^n A_iB_{n-i} \] 基本思路为先将系数表达 -> 点值表达 \(O(nlogn)\) 随后点值 \(O(n)\) 进行乘法运算 最后将点值表达 -> 系数表达 \(O(nlogn)\) 代码 #include<cstdio> #inc

luogu3803 多项式乘法 (FFT)

FFT讲解传送门 简单记一下做法: 1.算法流程:两式的系数表达转化为点值表达(O(nlogn))->利用点值表达使两式相乘(O(n))->将结果的点值表达转化回系数表达(O(nlogn)) 2.做法: $$目标:把一个n项多项式F(x)=\sum_{i=0}^{n-1}a_ix^i转化为\{(w^k_n,y_k)\}的点值表达,其中w^k_n为n次单位根的k次方$$ 不妨设n为2的幂次,如果不是,则可以补上系数为0的高次项 $$将a_ix^i按照幂次奇偶性分组,得到F(x)=(a_0x^0+

多项式相关——FFT(学习中……持续更新)

FFT 参考blog: 十分简明易懂的FFT(快速傅里叶变换) 快速傅里叶变换(FFT)详解 系数表示法 一个一元\(n\)次多项式\(f(x)\)可以被表示为:\[f(x) = \sum_{i = 0}^{n}a_{i}x^{i}\] 即用\(i\)次项的系数来表示\(f(x)\),展开就是\(f(x) = {a_{0}, a_{1}...a_{n}}\) 点值表示法 把多项式看做一个函数,然后带入\(n\)个不同的\(x\),可以得到\(n\)个不同的\(y\),每对\((x, y)\)就组