1. 程式人生 > >錯排問題 組合數學+容斥原理

錯排問題 組合數學+容斥原理

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
*/