錯排問題 組合數學+容斥原理
阿新 • • 發佈:2019-01-07
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。
60分:
【題目描述】
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
*/