容斥与计数
集合计数
容斥入门。
考虑交集大小一定至少为 k 的答案,那么首先是 C(n,k),选出 k 个元素;其次是看包含这 k 个元素的集合有多少个,为 \(2^{n-k}\) 个,接下来是从这些集合中选任意个(0 个除外),所以是 \(2^{2^{n-k}}-1\) 种选法;所以 k 时的答案就是 \(C(n,k)\cdot (2^{2^{n-k}}-1)\)。
目标是求恰好 K 时的答案,所以二项式反演,答案即为 \(\sum_{k=K}^{n}(-1)^{k-K}C(k,K)C(n,k)\cdot (2^{2^{n-k}}-1)\)。
int ans=0;
for(int i=K;i<=n;i++)(ans+=((i-K&1)?mod-1ll:1ll)*C(i,K)%mod*C(n,i)%mod*(qp(2,qp(2,n-i,mod-1))-1)%mod)%=mod;
cout<<(ans+mod)%mod;
ABC214G Three Permutations
这是错排问题的升级版。原错排问题只有一个限制,现在变成了两个,可以设想有 3 个限制时……目前看来不可做。先把这个题说了。
将每一个 (p[i],q[i]) 的限制转换到图上 (p[i],q[i]) 的一条有向边,图上的点对应的是数值,图上的边代表的是位置。我们可以立刻观察到这个图的性质是点数等于边数,且每个点的出入度均等于一,所以是由若干个环(或孤立点自环)构成的图。
由于这是一个计数题,我们可以考虑容斥,求出至少有 k 个位置一定与限制冲突时的方案数。转换成图上语言即:选 k 条边,每条边被分配到它的端点之一,同一节点不能分给两条边,问选法方案数。
首先,我们不难发现每个连通块可以分开考虑。对于自环,明显 k=0/1 时答案是 1,其余答案是 0;设这个环有 c 个点。
这个问题可以进一步转化为:取每条边的中点,现在我们有 2c 个点,在这 2c 个点构成的环上选择 k 对相邻的点,且这些相邻对均不共端点,有多少种选法。这个问题如果发生在序列上,是一个经典的组合数问题:先预留出 k 个,在剩下的 2c-k 个点当中选取 k 个作为右端点,把预留的 k 个点分别插入每个右端点之前即可,因此选法有 C(2c-k,k) 种;但是现在是环,上述解答只考虑了其中一种情况,就是 (1,2c) 这个相邻对不选,那么另一种情况就是钦定它选,于是 1 和 2c 不可用,剩下的 2c-2 个点依葫芦画瓢是 C(2c-2-(k-1),k-1) 种,即 C(2c-k-1,k-1)。
接下来我们只需要用背包分配每个连通块出现几个冲突,最后得到 f[k]·(n-k)! 表示总共至少一定发生 k 次冲突的方案数,根据二项式反演,可以知道最终答案为 \(\sum_{i=0}^n(-1)^i f[i]\cdot (n-i)!\)。
#include <bits/stdc++.h>
using namespace std;
const int N=3005,mod=1e9+7;
int n,tot,cnt,p[N],q[N],f[N][N],g[N],C[N*2][N],jc[N*2];
vector<int>G[N];
bool vis[N];
void dfs(int x){
vis[x]=1,cnt++;
for(int y:G[x])if(!vis[y])dfs(y);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&p[i]);
for(int i=1;i<=n;i++)scanf("%d",&q[i]);
for(int i=1;i<=n;i++)G[p[i]].emplace_back(q[i]);
jc[0]=1;
for(int i=1;i<=2*n;i++)jc[i]=1ll*jc[i-1]*i%mod;
C[0][0]=1;
for(int i=1;i<=2*n;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
f[0][0]=1;
for(int i=1;i<=n;i++)if(!vis[i]){
tot++,cnt=0,dfs(i);
memset(g,0,sizeof g);
if(cnt==1)g[0]=g[1]=1;
else for(int j=0;j<=cnt;j++){
g[j]=(C[2*cnt-j][j]+(!j?0:C[2*cnt-j-1][j-1]))%mod;
}
for(int j=cnt;~j;j--)for(int k=n;k>=j;k--)
f[tot][k]=(f[tot][k]+1ll*f[tot-1][k-j]*g[j]%mod)%mod;
}
int ans=0;
for(int i=0;i<=n;i++)
ans=(ans+((i&1)?-1ll:1ll)*f[tot][i]*jc[n-i]%mod)%mod;
cout<<(ans+mod)%mod;
}
已经没有什么好害怕的了
首先 if(n+K&1){puts("0");return 0;}
,然后 t=(n+K)/2
就是 a>b 的组数。
考察性质——看到'>'大于号(大小关系的比较)——联想到排序
a排序,b也排序(递增),之后清晰地看到所有b[j]<a[i]的j就在前缀,一起扫就能统计,设有c个j使b[j]<a[i]
那么会发现对于i-1的所有满足条件的j,一定满足i
接下来觉得很可以dp,设计dp状态,考察关键词,第一个肯定是前缀i,第二个是我们要选出的组数,叫它j吧,所以g[i][j]就表示考虑a[1~i],匹配了j组,有多少种方案
转移:1.不匹配a[i],g[i-1][j] 2.匹配a[i],那么留给它的选项有c-(j-1)个。所以总共 g[i][j]=g[i-1][j]+(c-j+1)g[i-1][j-1]
这样我们完成了选出t个组的工作,但是其他的位置怎么分还没有定,所以要×一个(n-t)!,但是这样一来就会导致许多的重复算,具体来说是一定存在t个匹配,但实际可能比t个多,套二项式反演公式就好了。
#include <bits/stdc++.h>
using namespace std;
const int N=2005,mod=1e9+9;
int n,t,K,a[N],b[N],g[N][N],C[N][N],jc[N];
int main(){
scanf("%d%d",&n,&K);
if(n+K&1){puts("0");return 0;}
t=(n+K)/2;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)scanf("%d",&b[i]);
jc[0]=1;
for(int i=1;i<=n;i++)jc[i]=1ll*jc[i-1]*i%mod;
C[0][0]=1;
for(int i=1;i<=n;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
sort(a+1,a+n+1),sort(b+1,b+n+1);
g[0][0]=1;
for(int i=1,c=0;i<=n;i++){
while(c+1<=n&&b[c+1]<a[i])c++;
for(int j=0;j<=n;j++){
g[i][j]=(g[i-1][j]+g[i-1][j-1]*(c-j+1ll)%mod)%mod;
}
}
int ans=0;
for(int i=t;i<=n;i++)ans=(ans+((i-t&1)?-1ll:1ll)*C[i][t]*g[n][i]%mod*jc[n-i]%mod)%mod;
cout<<(ans+mod)%mod;
}
[省选联考 2022] 卡牌
暴力容斥:设 \(f[i][s]\) 表示考虑了前 \(i\) 个数,不选 \(s\) 内质数的倍数,有几种方案。
诶……怎么没写完?
[AGC061C] First Come First Serve
有 \(n\) 个人在排队进门,第 \(i\) 个人在 \(A_i\) 时进 \(B_i\) 时出(保证先进去的先出来),每人须在进去或者出来时在名单的最后写上自己的名字,求最终名单有几种不同的顺序?\(n\le 5\times 10^5\),\(A_i,B_i\) 取遍 \([1,2n]\cap \mathbb{Z}\)。
先考虑答案是 \(2^{n}\),发现会算重。考虑什么时候会算重,发现如果一个区间完全独立于其他区间时随便取就会算重,两个区间 \([1,3]\) 和 \([2,4]\) 取 \(1,2\) 和 \(3,4\) 这两种情况也是算重的。总的来说,就是取 \(A\) 还是取 \(B\) 太任性了——考虑设计一种规则,使得严格遵循这种规则取就不会算重。
发现有一种规则可以达到这个条件:一开始全取 \(A\),如果你想改成取 \(B\) 必须要有人取区间 \((A,B)\) 里的数。但是,并不要求你改的时候 \((A,B)\) 里就有数,而是最后都定下来的时候对于每个取 \(B\) 的数都满足这个条件。
这个条件很苛刻,但不难发现确实不会算重了。由于只是最终的状态决定了它,我们很难按照时间线统计有多少种合法方案。题解给出的做法是容斥。
按照套路容斥有多少个不满足条件的取 \(B\) 的人。下面用记号 \(C_i=0\) 代表 \(i\) 取 \(A\),\(C_i=1\) 代表 \(i\) 取 \(B\)。
设 \(L_i\) 表示最小的 \(j\) 使得 \(B_j\ge A_i\),\(R_i\) 表示最大的 \(j\) 使得 \(A_j\le B_i\)。那么如果 \(i\) 不满足条件,一定有 \(C_{L_i\sim i-1}=0,C_{i+1\sim R_i}=1\),于是 \(C_{L_i\sim R_i}\) 就是固定的。观察到两个有交的区间 \([L_x,R_x]\) 和 \([L_y,R_y]\) 是不能同时不满足条件的,因此一种容斥局面的就是若干个不相交的 \([L,R]\) 组成的集合,一个局面的权值就是 \(2^{n-\sum len}(-1)^{|\{[L_i,R_i]\}|}\),所求就是所有合法局面的权值和。
自然用简单的容斥 DP 解决:设 \(f_i\) 表示完全包含于前缀 \(i\) 的局面们的权值和,转移:\(f_i=\sum_{x(R_x=i)}\left(\sum_{j=0}^{L_x-1}(-2^{-(R_x-L_x+1)}f_j)\right)-2^{n-(R_x-L_x+1)}\),边界 \(f_0=2^n\)。