前置知识:多项式全家桶

简介

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

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

定义(普通生成函数 OGF):\(A(x)=\sum\limits_{a\in\mathcal{A}}x^{|a|}=\sum\limits_{n\ge0}a_nx^n\)

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

OGF

鸽着(周末会补)

EGF

也鸽着(周末会补)

PGF

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

  • \(F(x)=\sum\limits_{i\ge0}P(X=i)x^i\)

  • \(F(1)=1\)

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

    • \(E\)里面的自变量代表事件(也就是加权)
  • \(E(x^{\underline{k}})=F^{(k)}(1)\)

    • 系数为下降幂可以通过多次求导得到。
  • \(E(x^2)=E(x(x-1)+x)=F''(1)+F'(1)\)

  • \(Var(x)=E(x^2)-E^2(x)=F''(1)+F'(1)-F(1)^2\)

    • \(Var(x)=E((x-E(x))^2)=E(x^2-2E(x)x+E^2(x))=E(x^2)-E^2(x)\)

    • \(var\)方差

[CTSC2006]歌唱王国

  • 题意
    给长为\(m\)\(S\),每次等概率在\([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\)递推式得,\(g_n=g_{n+1}+f_{n+1}\)
      系数转函数:注意左边\(*x\)常数项没有意义,但右边常数项为\(1\)要加上。
  • 方程2:\(G*(\frac{1}{c})^m=\sum\limits_{k=1}^ma_k*F*(\frac{1}{c})^{m-k}\)

    • 左边为\(G\)后面直接补了\(T\)但可能在补\(T\)中途就结束了,此时一定会出现前缀=后缀的情况,\(a[i]\)表示\(T\)长度为\(i\)的前后缀是否匹配,因此枚举重叠(相等)长度\(k\)
  • 然后解方程的套路通常是赋值\(x=1\)
    因为要求\(F'(x)\),方程1求导,且赋值\(x=1\)得到:\(F'(1)=G(1)\)
    方程\(2\)同样赋\(1\)得:\(G(1)=\sum\limits_{i=1}^ma_i*c^i\)
    因此跑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\)的串\(T_i\),只要任意一个串出现即可停止。
  • 思路
    同样,设\(F_i\)为在以\(T_i\)结尾结束的pgf,\(G\)为还未结束的pgf。
    易得方程1: \(\sum\limits_{i=1}^nF_i=1\)
    同上一道题,方程2: \(\sum\limits_{i=1}^nF_i+G=xG+1\)
    方程三的构造也和上一道题一样的,但要知道是哪两个字符串的border。
    因此记:\(a[i][j][k]\):为\(T_i\)前缀是否和\(T_j\)前缀长\(k\)处匹配。
    方程3:\(G*2^{-m}x^m=\sum\limits_{j=1}^n\sum\limits_{k=1}^ma_{i,j,k}F_j(x)2^{k-m}x^{m-k}\)
    代入\(x=1\)得:\(G(1)=\sum\limits_{j=1}^n\sum\limits_{k=1}^ma_{i,j,k}F_j(1)2^k\)
    方程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构造

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

无标号Powerset构造

  • 组合意义:01背包
  • 算法:(改版欧拉变换,polya指数)
    \(\bar{\xi}(A(x))=\prod\limits_{i\ge0}(1+x^i)^{a_i}=\exp (\sum\limits_{i\ge1}(-1)^{j-1}\dfrac{A(x^j)}{j})\)
    例题「SWTR-03」Counting Trees:较板,比较有趣

未完待续……+