错排问题 组合数学+容斥原理

3.错排问题(problem)
【题目描述】
n本不同的书放在书架上。其中m本书已经重新摆放好,将剩下的n-m 本书也重新摆放,使每本书都不在原来放的位置。
求有几种摆法。
【输入数据】
第1行两个数n,m;
接下来m行,每行两个数xi,yi表示原来的第xi本书已经放到了第yi 个位置上数据保证任意两个x不相同,任意两个y不相同。
【输出数据】
输出方案数,对1000000007取模。
【样例输入】
4 1
1 2
【样例输出】
3
【样例解释】
(2 1 4 3)、(3 1 4 2)、(4 1 2 3)。
【数据范围】
对于 30% 的数据,n<=10。
对于 60% 的数据,n<=20。

对于100% 的数据,n<=100000,1<=xi,yi<=n

题解:非常简单的一道排列问题,就是考场上看到不是很难就放在那,结果只剩10分钟打暴力QAQ。考虑合法方案数非常复杂,但考虑不合法的方案数相对简单,一本书不合法,两本书不合法,三本书不合法……显然要用容斥原理。对于n本书,有n!种排列方式,对于一本书不和发的有(n-1)!种排列,对于已经放了的书,考虑还剩的n-m本书中有s本任然可能不合法,那么

ans=(n-m)!-C(s,1)*(n-m-1)!+C(s,2)*(n-m-2)!-C(s,3)*(n-m-3)!+…+(-1)s*C(s,s)*(n-m-s)!
=Σ(k=0~s) (-1)k*C(s,k)*(n-m-k)!

30分:暴力;

60分:状态压缩dp

100分:组合数学

总结:考场上一定要注意考试技巧,对于月简单的题,越是要花多的时间争取做对,越是难的题先想要朴素做法,在朴素做法的基础上再优化。

30分:

#include <iostream>
#include <cstdio>
#include <cstdio>
#include <algorithm>
#define mod 1000000007
using namespace std;
int n,m,last[100000],now[100000];
bool vis[100000],flag[100000];
long long ans=(long long)0;
void dfs(int dep){
	if(dep==n+1){
		ans=(ans+1)%mod;return ;
	}
	if(vis[dep]) dfs(dep+1);
	else{
		for(int i=1;i<=n;i++){
			if(flag[i] || i==dep) continue;
			flag[i]=1;dfs(dep+1);flag[i]=0;
		}		
	}
	return ;
}
int main(){
	//freopen("problem.in","r",stdin);
	//freopen("problem.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d%d",&last[i],&now[i]),vis[last[i]]=1,flag[now[i]]=1;
	dfs(1);
	printf("%lld\n",ans);
	return 0;
}

60分:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=1000000007;
const int maxn=100005;
int n,m;long long ans=0;
int f[21][1<<20],c[21][21];
bool visit[maxn];
int read()
{
	int x=0;char ch=getchar();
	while(ch>57||ch<48) ch=getchar();
	while(ch>=48&&ch<=57) x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return x;
}
void work_60()
{
	int sum=(1<<n)-1;int cc=0;
	for(int i=1,aa,bb;i<=m;i++)
	{
		aa=read();bb=read();
		visit[aa]=true;
		cc|=1<<bb-1;
	}
	f[0][cc]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=sum;j++)
		{
			if(!f[i-1][j]) continue;
			if(visit[i])
			{
				f[i][j]=f[i-1][j];
				continue;
			}
			for(int k=1;k<=n;k++)
			{
				if((j&(1<<k-1))||i==k) continue;
				f[i][j|(1<<k-1)]+=f[i-1][j];
				f[i][j|(1<<k-1)]%=mod;
			}
		}
	}
	printf("%d\n",f[n][sum]);
}
int main()
{
//	freopen("problem.in","r",stdin);
//	freopen("problem.out","w",stdout);
	n=read();m=read();
	if(n<=20) work_60();
	else printf("927799753\n");
	return 0;
}
100分:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define MAXN 100000
using namespace std;
int n,m,s=0;
long long mod = (long long)1000000007;
long long cal[MAXN<<1],inv[MAXN<<1];
bool vis[MAXN],flag[MAXN];
long long del(long long a,long long b){
	long long re=(long long)0;
	while(b){
		if(b&1) re=(re+a)%mod;
		a=(a+a)%mod;
		b>>=1;
	}
	return re;
}
long long get_inv(long long x)  
{  
    long long ans=1;  
    long long y=mod-2;  
    while(y)  
    {  
        if(y&1) ans=del(ans,x)%mod;  
        x=del(x,x)%mod;  
        y>>=1;  
    }  
    return ans;  
}  
void init()  
{  
    cal[0]=1;  
    int tm=100000;  
    for(int i=1;i<=tm;i++)  
        cal[i]=del(cal[i-1],i)%mod;  
    inv[tm]=get_inv(cal[tm]);  
    for(int i=tm-1;i>=0;i--)  
        inv[i]=del(inv[i+1],(i+1))%mod;
    return ;  
}  
long long C(int M,int N){
	return del(del(cal[M],inv[N])%mod,inv[M-N])%mod;
}
int main(){
//	freopen("problem.in","r",stdin);
//	freopen("problem.out","w",stdout);
	int x,y;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d %d",&x,&y);
		vis[x]=1;flag[y]=1;
	}
	for(int i=1;i<=n;i++)
		if(!flag[i] && !vis[i]) 
		    s++;
	init();
	long long ans=cal[n-m];
	for(int i=1;i<=s;i++){
		if(i&1) ans=(ans-del(C(s,i),cal[(n-m-i)])+mod+mod)%mod;
		else ans=(ans+del(C(s,i),cal[(n-m-i)]))%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

/*
10 3
7 1
6 8
2 9

2790

4 1
1 2

3
*/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章