【题目描述】
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
*/