组合计数
CF838D
题意
有\(n\)个位置排成一排,有\(m\)个人依次进场选位置,每个人一开始会选择一个方向,从左到右或从右向左,并选择一个位置,然后按他选择的方向入场并走到这个位置,从这个位置开始继续按他选择的方向走,直到遇到一个空位并坐下。如果一直找不到空位,他就会生气。求有多少种情况没有人生气。
\(1\leq m\leq n\leq 10^6\)。
solution
看成一个长度为\(n+1\)环,其中编号\(n+1\)的点为间隔点(不能被占据),每次每个人可以选一个起点要么逆时针要么顺时针走到第一个空位上。如果编号为\(n+1\)的点被人占据了显然不合法,否则肯定合法。
由于这是一个环,因此最终每个点没被占据的概率是相同的。即一个点被占据的概率是\(1-\frac{m}{n+1}\),乘以方案数\(2^m (n+1)^m\)即可。
CTSC2017 吉夫特
题意
给定一个长度为\(n\)的数列\(a_i\),求有多少个长度\(\ge 2\)的不上升子序列\(a_{b_1},a_{b_2},\cdots ,a_{b_k}\)满足
\(1\leq n\leq 211985,1\leq a_i \leq 233333\),所有\(a_i\)互不相同。
答案对\(10^9+7\)取模。
solution
显然根据 Lucas 定理,后一个\(a_i\)的二进制位应是前一个的超集。
令\(f_i\)表示以\(a_i\)结尾的答案,暴力的话就枚举前面所有\(a_i\)的子集转移即可。
考虑经典的优化,设\(g_{x,y}\)表示前\(9\)位恰好为\(x\),后\(9\)位是\(y\)的子集的所有\(a_j\)对应\(f_j\)的和,那么我们每次转移只需要枚举\(a_i\)的前\(9\)位的子集即可。更新只需要枚举\(a_i\)后\(9\)位的超集。
int a[222222];
int n;
const int B=1<<9;
const int S=B-1;
int g[555][555];
inline void add(int &x,int y){x+=y;x>=mod?x-=mod:1;}
signed main()
{
n=read();
R(i,1,n) a[i]=read();
int x,y,sum;
R(i,1,n)
{
x=a[i]/B,y=a[i]%B,sum=0;
for(int t=x^S;t;t=(t-1)&(x^S)) add(sum,g[t|x][y]);
add(sum,g[x][y]);add(sum,1);
for(int t=y;t;t=(t-1)&y) add(g[x][t],sum);add(g[x][0],sum);
}
int ans=0;
R(i,0,S) add(ans,g[i][0]);add(ans,mod-n);
writeln(ans);
}
Loj不等关系
题意
一个排列,相邻两个数有 > 或 < 的限制,求有多少种满足要求的排列。
\(n\leq 10^5\)。
solution
令 < 为正向。
令\(f(i,j)\)表示前\(i\)个数,当前有\(j\)个数被划分为一组的概率。
如果\(i-1\)和\(i\)是 < ,那么\(f(i,j)=\frac{f(i-1,j-1)}{j}\)。
否则\(f(i,1)=\sum f(i-1,j),f(i,j)=-\frac{f(i-1,j-1)}{j} (j>1)\)。
最终答案自然就是\(\sum f(n,j)\)。
暴力\(dp\)是\(O(n^2)\)的,但是可以发现上面这个\(dp\)状态很“浪费”,相当于强行多加了一维状态让转移做到\(O(1)\)。
直接令\(g(i)=\sum f(i,j)\)(即只考虑前\(i\)个数的概率),考虑如何转移:
枚举上一次 > 被切断是什么时候,那么就有:
\(cnt\)为前缀大于号数量。
后面的式子显然是个卷积,因此可以分治NTT。
具体:
(当然也可以求解微分方程然后 exp,但是并不一定跑得过分治NTT)
时间复杂度\(O(n\log ^2 n)\)。
const int _G=3;
const int invG=332748118;
char s[111111];
int n,m;
int cnt[444444];
int fac[444444],inv[444444],Finv[444444];
int G[444444];
int s1[444444],s2[444444],s3[444444];
inline void init_FAC(int lim)
{
fac[0]=Finv[0]=inv[1]=1;
R(i,2,lim) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
R(i,1,lim) fac[i]=1ll*fac[i-1]*i%mod,Finv[i]=1ll*Finv[i-1]*inv[i]%mod;
}
int tr[444444],tf;
inline void tpre(int n)
{
if(tf==n) return;tf=n;
R(i,0,n-1) tr[i]=(tr[i>>1]>>1)|((i&1)?n>>1:0);
}
void NTT(int *g,int rev,int n)
{
tpre(n);
static ull f[888888],w[888888];w[0]=1;
R(i,0,n-1) f[i]=(((ll)mod<<5)+g[tr[i]])%mod;
for(int l=1;l<n;l<<=1)
{
ull tG=fpow(rev?_G:invG,(mod-1)/(l+l));
R(i,1,l-1) w[i]=w[i-1]*tG%mod;
for(int k=0;k<n;k+=l+l)
{
R(p,0,l-1)
{
int tt=w[p]*f[k|l|p]%mod;
f[k|l|p]=f[k|p]+mod-tt;
f[k|p]+=tt;
}
}
if(l==(1<<10)) R(i,0,n-1) f[i]%=mod;
}
if(!rev)
{
ull invn=fpow(n);
R(i,0,n-1) g[i]=f[i]%mod*invn%mod;
}
else R(i,0,n-1) g[i]=f[i]%mod;
}
void px(int *f,int *g,int *p,int n) {R(i,0,n-1)p[i]=1ll*f[i]*g[i]%mod;}
inline int calc(int x){return x&1?1:998244352;}
void cdq(int l,int r)
{
if(l==r)
{
if(!l) G[l]=1;
else G[l]=1ll*G[l]*calc(cnt[l]+1)%mod;
return;
}
int mid=(l+r)>>1,l_n=r-l+1,lim=1;
cdq(l,mid);
for(;lim<=l_n;lim<<=1);
clr(s1,lim+10),clr(s2,lim+10),clr(s3,lim+10);
cpy(s2,Finv,lim+5);
R(i,l,mid) s1[i-l]=1ll*G[i]*calc(cnt[i])%mod*(s[i]=='>');
NTT(s1,1,lim),NTT(s2,1,lim);
px(s1,s2,s3,lim);NTT(s3,0,lim);
R(i,mid+1,r) G[i]=(G[i]+s3[i-l])%mod;
cdq(mid+1,r);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>(s+1);n=strlen(s+1)+1;s[0]=s[n]='>';
init_FAC(400000);
R(i,1,n) cnt[i]=cnt[i-1]+(s[i]=='>');
cdq(0,n);
cout<<1ll*fac[n]*G[n]%mod<<endl;
}
Matrix-Tree 定理
Matrix-Tree 定理的本质是对环容斥。正常容斥系数应该是\((-1)^{环个数}\)。
因为是无向图所以可以随便钦定一个点是根节点,因此基尔霍夫矩阵去掉某个对角线元素的所在的行列答案是一样的。而对于除了根以外的每个点都要钦定一条出边,这样总共钦定了\(n-1\)条边。要求它是一棵树的方案,就把它成环的方案减掉。最基本的想法就是容斥,容斥产生了多少个环,暴力的话就去枚举钦定产生了若干个环,剩下的点就钦定出来根之后任选出边,容斥系数为\((-1)^{环个数}\)。
然后考虑行列式的定义,相当于任取一个排列出来,把排列全部乘起来再乘上\((-1)^{逆序对个数}\)。一个定义是对于一个排列,每次交换任意两个数字,最后交换成$1,2,3,4,5,\ldots $奇排列经历的步数一定是奇数,偶排列定义的步数一定是偶数。而逆序对个数可以决定它是奇排列还是偶排列。
然后考虑一个排列会形成若干个置换环,然后考虑它要交换多少次才能回到初始的排列,答案即:n-环个数次。因为每个置换环都要交换环长-1次,所以要交换n-环个数次才能回到初始排列。因此最后的\((-1)\)指数上逆序对个数也可以理解为n-环个数。
基尔霍夫矩阵对角线上全部是度数。
Matrix-Tree 定理删掉基尔霍夫矩阵中根所在的行列后,任意一个排列如果某一位选了它自己(即选在对角线上),那就说明从出边中随便选了一条;否则剩余的部分会形成若干个长度\(> 1\)的置换环(事实上这些就对应了原图的一个环(这是一个邻接矩阵)),如果这些环形成的是奇排列则会有\(-1\)的系数。
奇排列就相当于环的总长度减掉环个数是奇数,由于非对角线元素都取了相反数,因此\((-1)\)被乘的次数恰好是环的总长度,再乘上奇排列的系数,恰好就是\((-1)^{环个数}\)。
总结一下就是用行列式的定义:每次选择一个排列,这些排列与原图的选法一一对应,然后它乘的系数恰好就是\((-1)^{环个数}\)。
因此 Matrix-Tree 定理不仅可以用于无向图,也很容易可以推广到任意有向图上。
关于 Matrix-Tree 定理在有向图上的一个例子:

现在要求内向生成树个数。就可以将它的基尔霍夫矩阵写出来。(对角线上都是出度,因为是选内向生成树,每个点要钦定出边。)这里将根删掉了,因为无向图可以删掉对角线上点对应的行列是因为每个点都可以作为根,而有向图只能删掉根所在的行列。
最后只要算一下这个矩阵的行列式即可。
LGV 引理
题意
一个 DAG ,给定\(n\)个起点\(a_1,a_2,\cdots,a_n\)和\(n\)个终点\(b_1,b_2,\cdots ,b_n\),求选出\(n\)条路径\((a_1,b_1),\cdots,(a_n,b_n)\)且这些路径互不相交(不经过同一个点多次)的方案。
\(n\leq 500\)
solution
设\(f(a,b)\)表示\(a\to b\)的方案数,那么答案就是下面的行列式:
证明考虑如果最终路径产生了\(k\)个交点,那么可以决策这\(k\)个交点的两条出边分别交不交换,只要交换一次,最终的终点序列必然也会恰好交换一次,即改变排列的奇偶性。
因此若\(k>0\),则最终能够交换得到的奇排列和偶排列的数量一样(可以一一对应)
当且仅当\(k=0\)(即不产生交点)的时候,会被统计进答案。
模板题
https://www.luogu.com.cn/problem/P6657
题意
\(m\)个棋子,初始分别在\((a_i,0)\),最终分别要走到\((b_i,n)\),每次只能向右向上走,求路径不交的方案数。
\(m\leq 100,n\leq 10^6\)。
solution
由于每次只能向右向上走,因此是个 DAG ,直接应用 LGV 引理即可。
任意两点之间路径的方案数是个组合数,因此复杂度\(O(n+m^3)\)。
生成函数
基本操作
多项式求逆
给出\(P(x)\),要求\(Q(x)\)使得\(P(x)Q(x)\equiv 1 \pmod {x^n}\)。
假设已经求出了\(P(x)H(x)\equiv 1\pmod {x^k}\)现在求\(P(x)Q(x)\equiv 1\pmod {x^{2k}}\)
把\(1\)减到左边两边同时平方,得\(P^2(x)H^2(x)+1-2P(x)H(x)\equiv 0\pmod {x^{2k}}\),整理一下\(Q(x)=2H(x)-P(x)H^2(x)\)。
因此倍增即可,复杂度\(O(n\log n)\)。
多项式\(\ln\)
注意到\(\frac{d\ln F(x)}{dx}=\frac{F'(x)}{F(x)}\),因此做一次多项式求逆和多项式乘法,再积分即可。
复杂度\(n\log n\)。
多项式\(\exp\)
给定\(F(x)\),求\(G(x)=e^{F(x)}\)。
假设已知\(P(x)=e^{F(x)} \pmod {x^k},G(x)=e^{F(x)}\pmod {x^{2k}}\),则在\(x_0=P(x)\)处展开\(\ln G(x)\),得:
因此\(G(x)=P(x)+\left(F(x)-\ln P(x)\right)P(x) \pmod {x^2k}\)。
(这里使用泰勒展开而并不是牛顿迭代推导便于了解为什么牛顿迭代每次精度可以乘\(2\))
因此也可以倍增,复杂度\(O(n\log n)\)但常数非常大。
以及还可以写更好写复杂度为\(O(n\log^2 n)\)的分治FFT,由于常数小所以并不会比牛顿迭代慢。
具体可以看这 https://www.cnblogs.com/hizeci/p/14886812.html#求-1
多项式除法
给定\(A(x),B(x)\),求\(P(x),Q(x)\)满足\(A(x)=B(x)P(x)+Q(x)\),其中\(A,B\)分别为\(n,m\)次多项式,\(P,Q\)分别为\(n-m,m-1\)次多项式
把左右两边的多项式同时反转系数(reverse),则:
则两边同时对\(x^{n-m+1}\)取模后,多项式求逆即可解出\(P(x)\)。
则\(Q(x)=A(x)-B(x)P(x)\)。
时间复杂度\(O(n\log n)\)。
多点求值
设要求\(F(x)\)在\(x_1,x_2,\cdots x_k\)的点值,则让\(F(x)\)对多项式\((x-x_1)\cdot(x-x_2)\cdot \ldots \cdot(x-x_k)\)取模之后答案显然不变。
大概就是令\(P(x)=(x-x_1)\cdot (x-x_k)\cdot \ldots \cdot (x-x_k)\)。
\(F(x)=P(x)R(x)+Q(x)\),\(Q(x)\)为取模后的结果。可以发现代入任意\(x_1,\cdots ,x_k\),前面的\(P(x)R(x)\)等于\(0\),所以说\(F(x)\)在\(x_1,\cdots ,x_k\)处的点值与\(Q(x)\)在\(x_1,\cdots ,x_k\)的点值一样。
因此分治,每次仅对要求值的部分取模,然后递归下去处理。
总复杂度\(O(n \log^2 n)\)
快速插值
考虑拉格朗日插值的算式:
首先我们要对于每个\(i\)求出\(\prod\limits_{i\neq j} (x_i-x_j)\),这等于如下极限:
因此先算出括号内的多项式,求导后多点求值即可。
接下来要算类似于\(\sum d_i \prod\limits_{j\neq i}(x-x_j)\)的东西,直接分治fft即可。
总时间复杂度\(O(n\log^2 n)\)。但常数巨大。
[HAOI2018]染色
题意
\(n\)个位置,\(m\)种颜色,给定正整数\(s\),对于每个\(0\leq k\leq m\)求出恰有\(k\)种颜色恰好出现\(s\)次的方案数。
\(n\leq 10^7,m\leq 10^5\)
sol
固定一个K,考虑如何求答案。
枚举至少有多少种颜色恰好出现了s次,进行容斥。
过程:
考虑容斥,计算出恰好出现了\(s\)次的颜色至少有\(i\)种的方案数\(f_i\),钦定\(i\)种颜色正好放\(S\)种
有\(m\)种颜色选\(i\)种,所以有一个\(\binom m i\)
然后这\(n\)个位置分成\(i+1\)个部分:被钦定的\(i\)中颜色,每个有\(S\)个;剩下的\(m-i\)种颜色,一共\(n-i\times S\)个
先看作是可重的全排列数,那么方案就有\(\frac {n!}{(S!)^i\times(n-iS)!}\)前面各部分都是只有一种颜色,后面部分有\(m-i\)种取法,所以还有一个\((m-i)^{n-iS}\).
综上$f_i=\frac{n!}{(S!)^i \times (n-iS)!}\binom m i (m-i)^{n-iS} $
拆开得:
设\(A_i=i!\times f_i,B_i=\frac{(-1)^i}{i!}\)
有\(G_k=\frac{1}{k!}\sum\limits_{i=k}^{m}A[i]\times B[i-k]\)
上式只与变量\(i\)与\(i-k\)有关,显然是卷积形式。复杂度\(m \log m\)
范德蒙行列式
当范德蒙行列式中\(a_i\leq 10^6\)时可以一次卷积求出行列式。但当\(a_i\leq 10^9\)时就不能这么做了。
注意到范德蒙矩阵的行列式为\(\prod\limits_{i<j} (x_j-x_i)\),我们考虑取一个正负\(1\)的系数然后平方,让它变成:\(\prod\limits_{i\neq j}(x_j-x_i)\)。大概就是:
这正是我们在快速插值的时候要求的东西。
因此范德蒙行列式可以\(O(n\log^2 n)\)求出。

浙公网安备 33010602011771号