前置知识:多项式全家桶

简介

定义(组合对象):满足某一性质的树、图、串等可数的对象。

定义(组合类):同一性质的组合对象组成的集合,通常用花体字母如A表示。

定义(普通生成函数 OGF):A(x)=aAx|a|=n0anxn

xn为下标=占位符(保存组合类性质,是生成函数构造的核心),组合意义上叫(组合类的)大小an表示[xn]A(x),即大小为n的组合对象的总数目,通俗来说就是你题目想要求的东西,也可以说生成函数是对于序列a而言的。可以看出第一个等号后是组合意义(也是更直观于题目表现),第二个等号后是多项式,即数学意义。

OGF

鸽着(周末会补)

EGF

也鸽着(周末会补)

PGF

P()为表达式为真的概率。X为离散型随机变量也就是事件。

  • F(x)=i0P(X=i)xi

  • F(1)=1

    • 因为所有事件的概率和为1
  • E(x)=F(1)

    • E里面的自变量代表事件(也就是加权)
  • E(xk_)=F(k)(1)

    • 系数为下降幂可以通过多次求导得到。
  • E(x2)=E(x(x1)+x)=F(1)+F(1)

  • Var(x)=E(x2)E2(x)=F(1)+F(1)F(1)2

    • Var(x)=E((xE(x))2)=E(x22E(x)x+E2(x))=E(x2)E2(x)

    • var方差

[CTSC2006]歌唱王国

  • 题意
    给长为mS,每次等概率在[1,c]中随机一个数加到T里(T初始为空),直到T中有子串S停止,输出期望|T|的后四位。

  • 思路
    这种题就是套路吧,不懂套路感觉没法做的。
    F(x)为恰好n次结束的PGF,G(x)n次时还没结束的PGF。
    需要G是因为F不好求,而有了G就可以通过F,G的关系,列两个等式(方程)来解F
    目标是E(x)=F(x)

  • 方程1:F+G=xG+1

    • g递推式得,gn=gn+1+fn+1
      系数转函数:注意左边x常数项没有意义,但右边常数项为1要加上。
  • 方程2:G(1c)m=k=1makF(1c)mk

    • 左边为G后面直接补了T但可能在补T中途就结束了,此时一定会出现前缀=后缀的情况,a[i]表示T长度为i的前后缀是否匹配,因此枚举重叠(相等)长度k
  • 然后解方程的套路通常是赋值x=1
    因为要求F(x),方程1求导,且赋值x=1得到:F(1)=G(1)
    方程2同样赋1得:G(1)=i=1maici
    因此跑KMP+求解G(1)即可。

  • code:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int mod=1e4;
int pw[N];
int fail[N],s[N];
int main() {
	int n,t;scanf("%d%d",&n,&t);
	while(t--) {
		int m;scanf("%d",&m);
		for(int i=1;i<=m;i++) {scanf("%d",&s[i]);}
//		fail[1]=0;
		for(int i=2,j=0;i<=m;i++) {
			while(j&&s[i]!=s[j+1]) {j=fail[j];}
			if(s[i]==s[j+1])j++;
			fail[i]=j;
//			printf("fail[%d]=%d\n",i,j);
		}
		pw[0]=1;for(int i=1;i<=m;i++) pw[i]=pw[i-1]*n%mod;
		int ans=0;
		for(int i=m;i;i=fail[i]) {
			ans=(ans+pw[i])%mod;
		}
		printf("%04d\n",ans);
	}
	return 0;
}

[SDOI2017]硬币游戏

  • 题意
    跟上一题不同点在于,有n个长为m的串Ti,只要任意一个串出现即可停止。
  • 思路
    同样,设Fi为在以Ti结尾结束的pgf,G为还未结束的pgf。
    易得方程1: i=1nFi=1
    同上一道题,方程2: i=1nFi+G=xG+1
    方程三的构造也和上一道题一样的,但要知道是哪两个字符串的border。
    因此记:a[i][j][k]:为Ti前缀是否和Tj前缀长k处匹配。
    方程3:G2mxm=j=1nk=1mai,j,kFj(x)2kmxmk
    代入x=1得:G(1)=j=1nk=1mai,j,kFj(1)2k
    方程2其实没什么用,联立方程1和3,n+1个未知数,n+1个方程,让Gauss告诉你答案。
  • code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const ll mod=1e9+7;
const int N=305;
const db eps=1e-8;
db pw_2[N],G[N][N];
ll H[N][N],pw2[N];
char s[N];
ll Hash(int i,int l,int r) {return ((H[i][r]-H[i][l-1]*pw2[r-l+1])%mod+mod)%mod;}

void Print(int n) {
	printf("matrix:~~~\n");
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n+1;j++) {
			printf("%.6lf ",G[i][j]);		
		}
		puts("");
	}
	printf("end matrix:~~~\n");
}

void Gauss(int n) {
//	Print(n);
	for(int i=1;i<=n;i++) {
		int r=i;
		for(int j=i+1;j<=n;j++)if(fabs(G[j][i])>fabs(G[r][i])){r=j;}
		if(r!=i)swap(G[i],G[r]);
		assert(fabs(G[r][i])>eps);
		for(int j=i+1;j<=n;j++) {
			db d=G[j][i]/G[i][i];
			for(int k=1;k<=n+1;k++) {G[j][k]-=G[i][k]*d;}
		}
	}
	for(int i=n;i>=1;i--) {
		G[i][n+1]/=G[i][i];
		for(int j=1;j<i;j++) {G[j][n+1]-=G[j][i]*G[i][n+1];}
	}
	for(int i=1;i<n;i++) {printf("%.7lf\n",G[i][n+1]);}
//	printf("G0 = %.6lf\n",G[n][n+1]);
}

int main() {
	int n,m;scanf("%d%d",&n,&m);
	pw_2[0]=pw2[0]=1;for(int i=1;i<=m;i++)pw2[i]=pw2[i-1]*2%mod,pw_2[i]=pw_2[i-1]*0.5;
	for(int i=1;i<=n;i++) {
		scanf("%s",s);
		for(int j=1;j<=m;j++) {H[i][j]=(H[i][j-1]*2+(s[j-1]=='T'))%mod;}
	}
	//boader
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++)for(int k=1;k<=m;k++) {
			if(Hash(i,1,k)!=Hash(j,m-k+1,m))continue;
			G[i][j]+=pw_2[m-k];
//			printf("(i=%d,j=%d,k=%d)\n",i,j,k);
		}
		G[i][n+1]=-1;
	}
	for(int j=1;j<=n;j++)G[n+1][j]=1;G[n+1][n+2]=1;
	Gauss(n+1);
	return 0;
}

构造

oi-wiki

无标号Multiset构造

  • 组合意义:完全背包,即构造A里元素所有可能组合。
  • 算法:(polya exp & Euler 变换)
    ξ(A(x))=i1(11xi)ai=exp(j1A(xj)j)
    例题
    1.洛谷 P4389 付公主的背包:直接欧拉变换取出系数即可。
    2.loj566:多重背包(有取的个数上界,推柿子跟前一题差不多,暂时鸽着)
    3.无标号无根树计数:欧拉变换+分治fft/牛顿迭代接方程
    ps.总体能掌握推柿子的套路,以及熟悉了解用途即可。

无标号Powerset构造

  • 组合意义:01背包
  • 算法:(改版欧拉变换,polya指数)
    ξ¯(A(x))=i0(1+xi)ai=exp(i1(1)j1A(xj)j)
    例题「SWTR-03」Counting Trees:较板,比较有趣

未完待续……+