前置知识:多项式全家桶
简介
定义(组合对象):满足某一性质的树、图、串等可数的对象。
定义(组合类):同一性质的组合对象组成的集合,通常用花体字母如表示。
定义(普通生成函数 OGF):
为下标=占位符(保存组合类性质,是生成函数构造的核心),组合意义上叫(组合类的)大小。表示,即大小为的组合对象的总数目,通俗来说就是你题目想要求的东西,也可以说生成函数是对于序列而言的。可以看出第一个等号后是组合意义(也是更直观于题目表现),第二个等号后是多项式,即数学意义。
OGF
鸽着(周末会补)
EGF
也鸽着(周末会补)
PGF
设为表达式为真的概率。为离散型随机变量也就是事件。
-
-
- 因为所有事件的概率和为。
-
- 里面的自变量代表事件(也就是加权)
-
- 系数为下降幂可以通过多次求导得到。
-
-
-
-
是方差
-
[CTSC2006]歌唱王国
-
题意
给长为的,每次等概率在中随机一个数加到里(初始为空),直到中有子串停止,输出期望的后四位。 -
思路
这种题就是套路吧,不懂套路感觉没法做的。
设为恰好次结束的PGF,为次时还没结束的PGF。
需要是因为不好求,而有了就可以通过的关系,列两个等式(方程)来解。
目标是 -
方程1:
- 由递推式得,
系数转函数:注意左边常数项没有意义,但右边常数项为要加上。
- 由递推式得,
-
方程2:
- 左边为后面直接补了但可能在补中途就结束了,此时一定会出现前缀=后缀的情况,表示长度为的前后缀是否匹配,因此枚举重叠(相等)长度。
-
然后解方程的套路通常是赋值。
因为要求,方程1求导,且赋值得到:。
方程同样赋得:
因此跑KMP+求解即可。 -
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]硬币游戏
- 题意
跟上一题不同点在于,有个长为的串,只要任意一个串出现即可停止。 - 思路
同样,设为在以结尾结束的pgf,为还未结束的pgf。
易得方程1:
同上一道题,方程2:
方程三的构造也和上一道题一样的,但要知道是哪两个字符串的border。
因此记::为前缀是否和前缀长处匹配。
方程3:
代入得:
方程2其实没什么用,联立方程1和3,个未知数,个方程,让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;
}
构造
无标号Multiset构造
- 组合意义:完全背包,即构造里元素所有可能组合。
- 算法:(polya exp & Euler 变换)
例题:
1.洛谷 P4389 付公主的背包:直接欧拉变换取出系数即可。
2.loj566:多重背包(有取的个数上界,推柿子跟前一题差不多,暂时鸽着)
3.无标号无根树计数:欧拉变换+分治fft/牛顿迭代接方程
ps.总体能掌握推柿子的套路,以及熟悉了解用途即可。
无标号Powerset构造
- 组合意义:01背包
- 算法:(改版欧拉变换,polya指数)
例题:「SWTR-03」Counting Trees:较板,比较有趣
未完待续……+
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)