多项式练习记录
CF755G PolandBall and Many Other Balls
一排 \(n(n\le 10^9)\) 个球,一个组可以包含一个球或者两个相邻的球,一个球只能分一个组。求从这些球中取出 \(k(k<2^{15})\) 组的方案数.
枚举两个球的组个数 \(i\) ,有
然后就不会了 这时候 George1123
来指导了:直接推理比较困难,考虑转化组合意义:重复的最多 \(k\) 个,那么就相当于从前 \(k\) 个中选择 \(i\) 个,再从(除了这 \(i\) 个)剩下的球中选择 \(k\) 个。
其实思想就是把重复的都钦定在前 \(k\) 个里面。
注意到容斥重点在于前后选择不会重复。那么令 \(p(i)\) 表示恰好 \(i\) 个重复, \(q(i)\) 表示至少 \(i\) 个重复的方案数(这样一个至少一个恰好的形式似乎很常用),那么不难写出式子:
二项式反演一下,有
考虑怎么计算 \(q(j)\) ,先从前面 \(k\) 个钦定出 \(i\) 个重复的,再从 \(i\) 个之外选出 \(k-i\) 个,然后在 \(k-i\) 个中任意选或者不选(也就是至少 \(i\) 个中,钦定了 \(i\) 个,选之外的东西),于是有式子
于是就能计算 \(f\) 了
看上去复杂度很靠谱,但是题目要求求所有的 \(f(i),i=1\sim k\) .
于是我又不会了 /kk
. George1123
:化下降幂啊!
求和符号后面就是标准卷积式了。卷完之后再给每个 \(f[i]\) 乘上 \(i!n^{\underline i}\) 即可。
最优解 rk2
随便跑跑 /cy
Code
using namespace Poly;
int n,k,F[M],G[M],fac[M],ifac[M],dfac[M],idfac[M];
void Init( int m )
{
fac[0]=1; for ( int i=1; i<=m; i++ ) fac[i]=1ll*fac[i-1]*i%Mod;
ifac[m]=power(fac[m],Mod-2); for ( int i=m-1; i>=0; i-- ) ifac[i]=1ll*ifac[i+1]*(i+1)%Mod;
dfac[0]=1; for ( int i=1; i<=m; i++ ) dfac[i]=1ll*dfac[i-1]*(n-i+1)%Mod;
idfac[m]=power(dfac[m],Mod-2); for ( int i=m-1; i>=0; i-- ) idfac[i]=1ll*idfac[i+1]*(n-i)%Mod;
}
int main()
{
n=read(); k=read(); int m=min(n,k); Init(m);
k++;
for ( int i=0; i<k; i++ )
{
F[i]=1ll*ifac[i]*idfac[i]%Mod; if ( i&1 ) F[i]=Mod-F[i];
G[i]=power(2,i); G[i]=1ll*G[i]*ifac[i]%Mod*ifac[i]%Mod;
}
Poly_Mul( k,k,F,G,G );
for ( int i=0; i<k; i++ ) G[i]=1ll*G[i]*fac[i]%Mod*dfac[i]%Mod;
for ( int i=1; i<k; i++ ) printf( "%d ",G[i] );
return 0;
}
P4921 [MtOI2018]情侣?给我烧了!
\(n\) 对CP随便坐两列座位,求恰好 \(k\) 对CP坐在一起的方案数。
看到恰好显然容斥。
设 \(F[n,k]\) 表示 \(n\) 对,恰好 \(k\) 对坐在一起的方案数,\(G[n,k]\) 表示 \(n\) 对,至少 \(k\) 对坐在一起的方案数。
那么有
直接上二项式反演
于是就可以直接求了w
Code
//Author: RingweEH
const int Mod=998244353,N=2010;
ll fac[N],ifac[N],pow2[N],F[N];
ll C( int n,int m ) { return fac[n]*ifac[m]%Mod*ifac[n-m]%Mod; }
void Init() {} //省略预处理逆元和2的幂次
int main()
{
Init();
for ( int i=0; i<=1000; i++ )
{
//F[n,0]=\sum_{i=0}^n(-1)^i\binom ni^2i!2^i(2(n-i))!
ll buf=1;
for ( int j=0; j<=i; j++ )
{
ll nw=C(i,j);
F[i]=(F[i]+nw*nw%Mod*buf%Mod*fac[j]%Mod*fac[(i-j)<<1])%Mod;
buf=buf*(Mod-2)%Mod;
}
}
int T=read();
while ( T-- )
{
int n=read(); ll ans=0;
for ( int i=0; i<=n; i++ )
{
ans=C(n,i);
ans=ans*ans%Mod*fac[i]%Mod*pow2[i]%Mod*F[n-i]%Mod;
printf( "%lld\n",ans );
}
//ans=\binom nk^2k!2^kF[n-k][0]
}
return 0;
}
UVA12298 Super Poker II
有一副扑克牌,对于每个合数 \(x\) ,均有四种花色面值为 \(x\) 的牌各一张,有些牌丢失了。对于 \(n=a\sim b\) ,求出:选出四张花色互不相同的牌且面值和为 \(n\) 的方案数。
对于四个花色(简写为 \(1\sim 4\) ),得到四个数列,\(A_1[i]\) 表示花色为 \(1\) ,面值为 \(i\) 的牌的数量。不难发现,\((A_1*A_2)[i]\) (加法卷积)就是取一张花色为 \(1\) 的牌和一张花色为 \(2\) 的牌,和为 \(i\) 的方案数(常见的背包+笛卡尔积思想),那么直接把 \(4\) 个式子卷起来就好了。不想写 FFT,于是去掏了一个大模数 /kel
.
多测不清空,WAWA
两行泪……
Code
//Author: RingweEH
#define I128 __int128
const int N=2e5+10,M=N<<2,LIMIT=5e4+10;
int NW=5e4;
const ll Mod=39582418599937;
ll power( ll a,ll b ) { ll res=1; for (;b;b>>=1,a=(I128)a*a%Mod) if (b&1) res=(I128)a*res%Mod; return res; }
inline void bmod( ll &x ) { x-=Mod; x+=(x<0)*Mod; }
inline void pmod( ll &x ) { x=(x+Mod); x-=(x>Mod)*Mod; }
namespace Poly //预处理,NTT
int tot=0,pri[LIMIT];
bool vis[LIMIT];
void Sieve( int n=LIMIT-10 ) //省略线性筛
int a,b,c;
ll S[M],H[M],C[M],D[M],Arr[M],r1[M],r2[M];
char s[10];
void exread()
{
int x=0; char ch=getchar();
while ( ch>'9' || ch<'0' ) ch=getchar();
while ( ch<='9' && ch>='0' ) x=x*10+ch-'0',ch=getchar();
if ( ch=='S' ) S[x]--;
else if ( ch=='H' ) H[x]--;
else if ( ch=='C' ) C[x]--;
else if ( ch=='D' ) D[x]--;
}
int main()
{
Sieve();
for ( int i=1; i<=LIMIT-10; i++ ) Arr[i]=(vis[i]) ? 1 : 0; Arr[1]=0;
while ( 1 )
{
a=read(); b=read(); c=read(); NW=min(b+10,50000);
if ( !a && !b && !c ) break;
memcpy(S,Arr,sizeof(ll)*NW); memcpy(H,Arr,sizeof(ll)*NW);
memcpy(C,Arr,sizeof(ll)*NW); memcpy(D,Arr,sizeof(ll)*NW);
while ( c-- ) exread();
Poly_Init(NW<<2);
NTT(S,1); NTT(H,1); NTT(C,1); NTT(D,1);
for ( int i=0; i<lim; i++ ) S[i]=(I128)S[i]*H[i]%Mod*C[i]%Mod*D[i]%Mod;
reverse(S+1,S+lim); NTT(S,0);
for ( int i=a; i<=b; i++ ) printf( "%lld\n",S[i] ); puts("");
for ( int i=0; i<lim; i++ ) S[i]=H[i]=D[i]=C[i]=0;
}
return 0;
}
P4451 整数的lqp拆分
对 \(1e9+7\) 取模。
从这道题就能看出 OI
事业的进步,连我都能做 2011
国集答辩了。
根据所学的“ 骨牌问题 ”,不难想到这里可以看成是长度(值)为 \(n\) 的骨牌有 \(\tt Fib_n\) 种,而斐波那契数列的生成函数是 \(\dfrac{1}{1-x-x^2}\) ,所以不难得到这个问题的封闭形式是 \(\dfrac{1}{1-\frac{x}{1-x-x^2}}=\dfrac{1-x-x^2}{1-2x-x^2}\) .
我们仿照斐波那契数列的推导,不难得到分母的递推式是 \(a_n=2a_{n-1}+a_{n-2}\) ,
将分母写成递推式,有 \(a_n=2a_{n-1}+a_{n-2}\) ,那么 \(ans=a_n-a_{n-1}-a_{n-2}=a_{n-1}\) .
用特征方程求解得到 \(q=1\pm \sqrt 2\) ,带入 \(n=0,1\) 解 \(F[n]=(1+\sqrt 2)^nx_0+(1-\sqrt 2)^nx_1\) (注意 \(ans=a_{n-1}\) )
于是有 \(a_n=\dfrac{2-\sqrt 2}{4}(1-\sqrt 2)^n+\dfrac{2+\sqrt 2}{4}(1+\sqrt 2)^n\) ,而 \(n\leq 1e10000\) ,所以再用费马小定理化简一下即可。模意义下的 \(\sqrt 2\) 用二次剩余不难求出,是 59713600
和 940286407
.
Code
//Author: RingweEH
const int Mod=1e9+7,Invs2=59713600;
int bmod( int x ) { x-=Mod; x+=(x>>31)&Mod; return x; }
int n;
int main()
{
n=Modread();
int t1=bmod(1+Mod-Invs2),t2=bmod(1+Invs2);
t1=power(t1,n-1); t2=power(t2,n-1);
t1=1ll*t1*bmod(2+Mod-Invs2)%Mod;
t2=1ll*t2*bmod(2+Invs2)%Mod;
int ans=bmod(t1+t2);
ans=1ll*ans*power(4,Mod-2)%Mod;
printf( "%d\n",ans );
return 0;
}
CF438E The Child and Binary Tree
称点权均在某个集合 \(C\) 中的有根二叉树为好的,求点权和为 \(s\) 的好的二叉树个数,对 \(998244353\) 取模。
令 \(G(x)=\sum x^{c_i},F(x)=\sum ans_ix^i\) 。
枚举一个节点的权值,再枚举左右儿子,对于 \(n>1\) ,有
所以 \(F(x)=1+G(x)*F^2(x)\) ,解得 \(F(x)=\dfrac{1\pm \sqrt{1-4G(x)}}{2G(x)}\) . 注意我们需要一个收敛的式子,所以由于 \(\lim\limits_{x\to 0}G(x)=0\) ,只能取负号。
最后有
直接开方逆元即可。
Code
//Author: RingweEH
using namespace Poly;
int n,m,F[M],G[M],ans[M];
int main()
{
n=read(); m=read()+1; Math::Init();
F[0]=1;
for ( int i=1,x; i<=n; i++ )
if ( (x=read())<m ) F[x]=1;
for ( int i=1; i<m; i++ ) bmod(F[i]=Mod-4ll*F[i]%Mod);
Poly_Sqrt(m,F,G); bmod(++G[0]);
Poly_Inv(m,G,ans);
for ( int i=0; i<m; i++ ) bmod(ans[i]<<=1);
for ( int i=1; i<m; i++ ) printf("%d\n",ans[i]);
return 0;
}
CF917D Stranger Trees
给定一棵 \(n\) 点无权无向树,求对于这 \(n\) 个点以及 \(k=0\sim n-1\) ,有多少棵由这 \(n\) 个点之间的边构成的树,与给定树恰好有 \(k\) 条边重复,对 \(1e9+7\) 取模。
恰好
——直接上二项式反演,问题转化为求至少 \(k\) 条边重合的方案数。那么就是选 \(k\) 条边,剩下 \(n-k\) 个连通块再任意连边。这就转化成了 Prufer序列小记 结尾的那个结论,方案数就是
我们可以给后面这个积找一个组合意义,也就是每个连通块内选一个点。那么一个树形DP就来了:设 \(f[i][j][k]\) 表示以 \(i\) 为根的子树,选了 \(j\) 个连通块,\(i\) 所在的连通块是否选点( \(k\) )的方案数,至少 \(k\) 条边重合的方案数就是 \(f[1][n-k][1]\) ,再反演回去即可。复杂度 \(\mathcal{O}(n^2)\) 。
好像正解是矩阵树定理?不管了
反正我不会
Code
//Author: RingweEH
#define pb push_back
const int N=110,Mod=1e9+7;
int power( int a,int b ) { int res=1; for ( ; b; b>>=1,a=1ll*a*a%Mod ) if ( b&1 ) res=1ll*res*a%Mod; return res; }
int n,F[N],G[N],C[N][N],f[N][N][2],g[N][2],siz[N];
vector<int> Ed[N];
void Init() //预处理组合数
void Add( int &x,int v ) { x=(x+v-Mod); x+=(x>>31)&Mod; }
void DFS( int u,int fa )
{
int (*nw)[2]=f[u]; nw[1][0]=nw[1][1]=1;
siz[u]=1;
for ( int t=0; t<Ed[u].size(); t++ )
{
int v=Ed[u][t];
if ( v==fa ) continue;
DFS(v,u); int (*las)[2]=f[v];
for ( int i=0; i<=siz[u]; i++ )
for ( int j=0; j<=siz[v]; j++ )
{
Add( g[i+j][0],1ll*nw[i][0]*las[j][1]%Mod );
Add( g[i+j][1],1ll*nw[i][1]*las[j][1]%Mod );
if ( i+j )
{
Add( g[i+j-1][0],1ll*nw[i][0]*las[j][0]%Mod );
Add( g[i+j-1][1],1ll*nw[i][1]*las[j][0]%Mod );
Add( g[i+j-1][1],1ll*nw[i][0]*las[j][1]%Mod );
}
}
for ( int i=0; i<=siz[u]+siz[v]; i++ )
f[u][i][0]=g[i][0],f[u][i][1]=g[i][1],g[i][0]=g[i][1]=0;
siz[u]+=siz[v];
}
}
int main()
{
n=read();
for ( int i=1,u,v; i<n; i++ )
u=read(),v=read(),Ed[u].pb(v),Ed[v].pb(u);
Init(); DFS(1,0);
for ( int i=0; i<n-1; i++ ) G[i]=1ll*f[1][n-i][1]*power(n,n-i-2)%Mod;
G[n-1]=1;
for ( int i=0; i<n; i++ )
for ( int j=i; j<n; j++ )
if ( (j-i)&1 ) Add(F[i],Mod-1ll*G[j]*C[j][i]%Mod);
else Add( F[i],1ll*G[j]*C[j][i]%Mod );
for ( int i=0; i<n; i++ ) printf( "%d ",F[i] ); puts("");
return 0;
}
HDU1521 排列组合
给定 \(n\) 种物品和数量,求选出 \(m\) 件的排列数。
仿照染色问题,对于有 \(num[i]\) 个的第 \(i\) 种物品,不难得到其 EGF
为 \(\sum\limits_{j=0}^{num[i]}\dfrac{x^j}{j!}\) ,各个颜色的 EGF
卷起来即可。\(n,m\) 只有 \(10\) ,暴力卷积就好了。
Code
//Author: RingweEH
memset( F,0,sizeof(F) ); memset( G,0,sizeof(G) );
for ( int i=1; i<=n; i++ ) num[i]=read();
for ( int i=0; i<=num[1]; i++ )
F[i]=1.0/fac[i];
for ( int i=2; i<=n; i++ )
{
for ( int j=0; j<=m; j++ )
for ( int k=0; k<=num[i] && j+k<=m; k++ )
G[j+k]+=F[j]/fac[k];
for ( int j=0; j<=m; j++ )
F[j]=G[j],G[j]=0;
}
printf( "%.0lf\n",F[m]*fac[m] );
P5219 无聊的水题 I
求 \(n\) 点、最大度数为 \(m\) 的树的个数。
最大度数为 \(m\) 就是 “恰好 \(m\) ” ,显然可以把它容斥掉。而关于度数的计树题很容易想到 Prufer
序列,度数不超过 \(m\) 就是在序列中每个点出现次数不超过 \(m-1\) 。现在问题转化为:一个长度为 \(n-2\) 的序列,每个元素的取值为 \([1,n]\) ,每个值出现次数不超过 \(m\) ,求方案数。
给 \([1,n]\) 来一个 EGF
,正好是 \(\sum_{i=0}^m\dfrac{x^i}{i!}\) ,并且全部一样,那就直接快速幂就好了。
最优解 rk1
了 /cy
.
Code
//Author: RingweEH
using Poly::Poly_Power;
int n,m,F[M],G[M];
int Get( int n,int m )
{
memset( F,0,sizeof(F) );
for ( int i=0; i<=m; i++ ) F[i]=Math::infac[i];
Clear(G,n); Poly_Power(n,n,F,G);
return 1ll*G[n-2]*Math::fac[n-2]%Mod;
}
int main()
{
Math::Init();
n=read(); m=read();
printf( "%d\n",(Get(n,m-1)-Get(n,m-2)+Mod)%Mod );
return 0;
}
P5339 唱、跳、rap和篮球
求合法排列数使得不会有恰好 \(1\sim 4\) 类的人排在一起。
设恰好有 \(1\sim 4\) 在 \(k\sim k+3\) 的情况叫 \(A\) ,那么题意就是恰好 \(0\) 个 \(A\) ,设 \(F[i]\) 表示至少有 \(i\) 个 \(A\) ,\(G[i]\) 表示恰好 \(i\) 个,上二项式反演。现在就是求出至少的个数。
\(calc\) 是 \(a-i,b-i,c-i,d-i\) 个人排成长度为 \(n-4i\) 的方案数,这个东西直接把 4
个 EGF
卷一卷就好了。于是这题就莫得了!
Code
//Author: RingweEH
using Math :: power;
using Math :: bmod;
using Math :: C;
using namespace Poly;
int Calc( int pos,int a,int b,int c,int d )
{
static int A[M],B[M],C[M],D[M]; Poly_Init(a+b+c+d);
for ( int i=0; i<a; i++ ) A[i]=Math::infac[i]; Clear(A+a,lim-a);
for ( int i=0; i<b; i++ ) B[i]=Math::infac[i]; Clear(B+b,lim-b);
for ( int i=0; i<c; i++ ) C[i]=Math::infac[i]; Clear(C+c,lim-c);
for ( int i=0; i<d; i++ ) D[i]=Math::infac[i]; Clear(D+d,lim-d);
NTT(A,1); NTT(B,1); NTT(C,1); NTT(D,1);
for ( int i=0; i<lim; i++ ) A[i]=1ll*A[i]*B[i]%Mod*C[i]%Mod*D[i]%Mod;
reverse(A+1,A+lim); NTT(A,0); return A[pos];
}
int n,a,b,c,d,F[510],G[510];
int main()
{
Math::Init();
n=read(); a=read(); b=read(); c=read(); d=read();
int lim=min(n>>2,min(a,min(b,min(c,d))));
for ( int i=0; i<=lim; i++ ) G[i]=Calc(n-4*i,a-i+1,b-i+1,c-i+1,d-i+1);
for ( int i=0; i<=lim; i++ ) F[i]=1ll*C(n-3*i,i)*G[i]%Mod*Math::fac[n-4*i]%Mod;
int ans=0;
for ( int i=0; i<=lim; i++ ) bmod(ans+=(i&1)?(Mod-F[i]):F[i]);
printf( "%d\n",ans );
return 0;
}
P4841 城市规划
求 \(n\) 点简单有标号无向连通图数。
数树完了来数图!注意到 “连通图” 不仅是一个限制更严格的图,同时也是一个简单有标号无向图(以下简称为一般图)的基本组成单位。那么如果连通图的 EGF
为 \(F(x)\) ,一般图的 EGF
就不难得到,也就是 \(\exp F(x)\) ,设为 \(G(x)\) ,那么 \(F(x)=\ln G(x)\) 。
问题转化为了求 \(G(x)\) 。一般图总共 \(\binom{n}{2}\) 条边,方案数就是 \(\displaystyle2^{\binom{n}{2}}\) ,直接上多项式 \(\ln\) 即可。
Code
//Author: RingweEH
using Poly:: Poly_ln;
int n,F[M];
int main()
{
n=read(); Math::Init();
for ( int i=0; i<n+1; i++ ) F[i]=power(2,(1ll*i*(i-1)/2)%(Mod-1)),F[i]=1ll*F[i]*Math::infac[i]%Mod;
Poly_ln( n+1,F,F );
printf( "%lld\n",1ll*F[n]*Math::fac[n]%Mod );
return 0;
}
P5162 WD与积木
\(n\) 个高为 \(1\) 的正方形积木,随机大小并标号。相同大小同一层,所有层从大到小对方,求不同堆法的层数期望。
对于每一层的积木,EGF
是 \(F(x)=\sum\limits_{i=1}\dfrac{x^i}{i!}=e^x-1\) ,那么一个 \(i\) 层的东西就是 \(F^i(x)\) ,总方案就直接求和:\(G(x)=\sum\limits_{i=0}F^i(x)=\dfrac{1}{1-F(x)}=\dfrac{1}{2-e^x}\) .
现在要算总贡献,那就是再乘个层数,然后随便差分:
算期望就直接总贡献除以方案数即可。
Code
//Author: RingweEH
using namespace Poly;
int n,F[M],G[M],tf[M],tg[M];
int main()
{
int n=100000;
Math::Init();
tf[0]=1;
for ( int i=1; i<=n; i++ ) tf[i]=Mod-infac[i];
for ( int i=1; i<=n; i++ ) G[i]=infac[i];
Poly_Inv(n+1,tf,F); Poly_Mul(n+1,n+1,tf,tf,tf );
Poly_Inv(n+1,tf,tg); Poly_Mul(n+1,n+1,G,tg,G);
int T=read();
while ( T-- )
{
n=read();
printf("%lld\n",1ll*G[n]*power(F[n],Mod-2)%Mod );
}
return 0;
}
P4548 歌唱王国
随机生成一个取值在 \([1,n]\) 的整数序列,给出 \(t\) 个序列,生成在出现一个钦定的序列后停止。问出现每个给定序列然后停止的序列长度期望。
一点不会,找了半天 PGF
的博客都说只能讲题……所以就来看题解了。
设 \(F[n]\) 为恰好在第 \(n\) 次结束的概率,\(G[n]\) 为到了第 \(n\) 次仍未结束的概率,答案就是 \(E(X)=F'(1)\) 。设 \(F,G\) 为二者的 PGF
,那么 \(F'(1)\) 即为所求。由于一个数要么结束,要么没结束,所以有式子:
求导得到
当 \(x=1\) 时,有 \(F'(1)=G(1)\) .
注意到,在一个没有结束的式子后面加上名字一定会结束,但是有可能在没有到整个名字就结束了(此时添加的序列是名字的一个 border),记字符集大小为 \(n\) ,名字长度为 \(m\) ,令 \(a[i]\) 表示 \([1,i]\) 是否是名字的一个 border ,那么可以列出式子:
当 \(x=1\) 时,有 \(G(1)=\sum\limits_{i=1}^ma[i]\cdot F(1)\cdot n^i=\sum\limits_{i=1}^ma[i]\cdot n^i\) 。
于是 \(F'(1)=\sum\limits_{i=1}^ma[i]n^i\) ,KMP 求出 \(a[i]\) 然后直接算即可。
一个 getchar()
的 fread
优化能松到 rk1
……
Code
//Author: RingweEH
const int N=1e5+10,Mod=10000;
int n,m,t,s[N],powe[N],nxt[N];
inline void bmod( int &x ) { x-=Mod; x+=(x>>31)&Mod; }
void Work()
{
m=read();
for ( int i=1; i<=m; i++ ) s[i]=read();
for ( int i=2,j=0; i<=m; i++ )
{
while ( j && s[j+1]!=s[i] ) j=nxt[j];
if ( s[j+1]==s[i] ) j++;
nxt[i]=j;
}
int ans=0;
for ( int i=m; i; i=nxt[i] ) bmod(ans+=powe[i]);
printf("%04d\n",ans );
}
int main()
{
n=read(); t=read();
powe[0]=1;
for ( int i=1; i<=N-10; i++ ) powe[i]=1ll*powe[i-1]*n%Mod;
while ( t-- ) Work();
return 0;
}
P3706 硬币游戏
给定 \(n\) 个长度为 \(m\) 的硬币序列,出现其中一个就停止,问等概率情况下每个人的胜率。
上一题的加强版,匹配串是多个而不是一个。但是注意到 \(n\) 并不大,所以可以直接开 \(n\) 个。
和上题一样,设 \([x^n]G(x)\) 表示 \(n\) 轮之后仍然没有结束的概率,\([x^n]F_k(x)\) 表示第 \(k\) 个人在第 \(n\) 轮获胜的概率。
同样地,仍然可以列出两个式子:
设 \(a[i][j][k]\) 表示第 \(i\) 个串的 \([1,\cdots ,k]\) 是否和第 \(j\) 个串的 \([m-k+1,m]\) 匹配。这个可以 Hash 直接求,反正数据不大。然后同样在后面接一个串,这里的 \(i\) 取值为 \([1,n]\) :
还是老办法,令 \(x=1\) 得到:
于是直接高斯消元即可。为什么这个存的下啊/jk
Code
//Author: RingweEH
const int N=310,base1=31,base2=37,Mod1=998244353,Mod2=1e9+7;
int n,m,has1[N][N],has2[N][N],powe1[N],powe2[N];
db a[N][N],powe[N],ans[N];
char s[N];
inline int bmod( int x,int Mod ) { x-=Mod; x+=(x>>31)&Mod; return x; }
int Geth1( int num,int l,int r )
{
return bmod(has1[num][r]-1ll*has1[num][l-1]*powe1[r-l+1]%Mod1+Mod1,Mod1);
}
int Geth2( int num,int l,int r )
{
return bmod(has2[num][r]-1ll*has2[num][l-1]*powe2[r-l+1]%Mod2+Mod2,Mod2);
}
void Gauss( int n )
{
for ( int i=1; i<=n; i++ )
{
int p=i;
for ( int j=i; j<=n; j++ )
if ( fabs(a[j][i])>fabs(a[p][i]) ) p=j;
if ( p^i )
{
for ( int j=1; j<=n+1; j++ ) swap( a[p][j],a[i][j] );
}
for ( int j=1; j<=n; j++ )
{
if ( i==j ) continue;
db tmp=a[j][i]/a[i][i];
for ( int k=i; k<=n+1; k++ ) a[j][k]-=a[i][k]*tmp;
}
}
for ( int i=1; i<=n; i++ ) a[i][n+1]/=a[i][i];
}
void Hash_Init()
{
powe[0]=powe1[0]=powe2[0]=1;
for ( int i=1; i<=m; i++ )
{
powe[i]=powe[i-1]*2.0;
powe1[i]=1ll*powe1[i-1]*base1%Mod1;
powe2[i]=1ll*powe2[i-1]*base2%Mod2;
}
for ( int i=1; i<=n; i++ )
{
scanf( "%s",s+1 ); has1[i][0]=has2[i][0]=0;
for ( int j=1; j<=m; j++ )
{
int c=(s[j]=='H')+1;
has1[i][j]=(1ll*has1[i][j-1]*base1%Mod1+c)%Mod1;
has2[i][j]=(1ll*has2[i][j-1]*base2%Mod2+c)%Mod2;
}
}
}
int main()
{
scanf( "%d%d",&n,&m);
Hash_Init();
for ( int i=1; i<=n; i++ )
{
for ( int j=1; j<=n; j++ )
for ( int k=1; k<=m; k++ )
if ( Geth1(i,1,k)==Geth1(j,m-k+1,m) && Geth2(i,1,k)==Geth2(j,m-k+1,m) )
a[i][j]+=powe[k];
a[i][n+1]=-1; a[i][n+2]=0; a[n+1][i]=1;
}
a[n+1][n+1]=0; a[n+1][n+2]=1;
Gauss(n+1);
for ( int i=1; i<=n; i++ ) printf( "%.10lf\n",a[i][n+2] );
return 0;
}
HDU4652 Dice
\(m\) 面的骰子和两种询问,问出现 \(n\) 个相同结果的期望和出现 \(n\) 个不同结果的期望。
相对于上题增加了字符集,改为两种匹配串。
我总觉得这题用 PGF 有亿点麻烦 设 \(F[i]\) 为第 \(i\) 步结束的概率,\(G[i]\) 反之。无论是第一问还是第二问,每个串都是等价的,所以可以先统一考虑,设终止串的总数有 \(cnt\) 个,那么在某个串结束的 PGF
就是 \(\dfrac{F(x)}{cnt}\) 。
同样,设 \(a[i]\) 表示长为 \(i\) 的后缀等于加入串 \(S[1,i]\) 的个数。不难列出式子:
将 \(x=1\) 代入得:
对于第一问,\(cnt=m,a[i]=1\) ;对于第二问,\(cnt=m^{\underline{n}},a[i]=(m-i)^{\underline{n-i}}\) ,代入求值即可。