Description:
In combinatorics a permutation of a set S with N elements is a listing of the elements of S in some order (each element occurring exactly once). There are N! permutations of a set which has N elements. For example, there are six permutations of the set {1,2,3}, namely [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], and [3,2,1]. But Bob think that some permutations are more beautiful than others. Bob write some pairs of integers(Ai, Bi) to distinguish beautiful permutations from ordinary ones. A permutation is considered beautiful if and only if for some i the Ai-th element of it is Bi. We want to know how many permutations of set {1, 2, …., N} are beautiful.
Input:
The first line contains an integer T indicating the number of test cases.
There are two integers N and M in the first line of each test case. M lines follow, the i-th line contains two integers Ai and Bi.
Technical Specification :
1. 1 <= T <= 50
2. 1 <= N <= 17
3. 1 <= M <= N*N
4. 1 <= Ai, Bi <= N
Output:
For each test case, output the case number first. Then output the number of beautiful permutations in a line.
Sample Input:
3
3 2
1 1
2 1
3 2
1 1
2 2
4 3
1 1
1 2
1 3
Sample Output:
Case 1: 4
Case 2: 3
Case 3: 18
题意:
给你一个 1-N 的集合,求满足任意一个条件的排列有多少种?
条件为:第
Ai
个数字是
Bi
。
题解:
如果直接求解,会涉及很多容斥,非常麻烦,我们可以反过来考虑,找到那些一个条件也不满足的排列,再用总数(
n!
)减去它们就是最终答案了。
实现我们可以用状压DP,朴素一点的可以用 dp[ i ][ j ]表示到当前第 i 位,每个数的使用情况为 j 的方案数,但由于空间限制,必须要用滚动数组优化,比较简单;
当然我们也可以考虑降一维,只定义dp[ j ],这是一个常规套路,就是每次从大到小枚举 j,那么更新 dp[ j ] 时用到的 更小的 dp[ j’] 就是i-1 的值,就可以直接更新
方法一(一维)
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<iomanip>
#include<iostream>
#include<cctype>
using namespace std;
int T,n,m,cas,x,y;
long long fact[20],dp[1<<18];
bool g[18][18];
inline int Readint(){
int i=0;char c;
for(c=getchar();c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';c=getchar()) i=(i<<1)+(i<<3)+c-'0';
return i;
}
inline void pre(){
fact[0]=1;
for(int i=1;i<=17;i++)
fact[i]=fact[i-1]*i;
}
int main(){
// freopen("lx.in","r",stdin);
pre();
T=Readint();
while(T--){
memset(dp,0,sizeof(dp));
memset(g,0,sizeof(g));
n=Readint(),m=Readint();
for(int i=1;i<=m;i++){
x=Readint(),y=Readint();
x--,y--;
g[x][y]=true;
}
dp[0]=1;
int mx=(1<<n)-1;
for(int i=0;i<n;i++){
for(int j=mx;j>=0;j--){
if(dp[j]==0) continue;
for(int k=0;k<n;k++){
if((j&(1<<k))!=0) continue;
if(g[i][k]==1) continue;
dp[j|(1<<k)]+=dp[j];
}
}
}
cout<<"Case "<<++cas<<": "<<fact[n]-dp[mx]<<endl;
}
return 0;
}
方法二(朴素)
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<cstring>
#include<string>
#include<cctype>
using namespace std;
int n,m,x,y,T,cas;
long long fact[18],dp[2][1<<18];
bool g[18][18];
inline void pre(){
fact[0]=1;
for(int i=1;i<=17;i++)
fact[i]=i*fact[i-1];
}
int main(){
// freopen("lx.in","r",stdin);
pre();
scanf("%d",&T);
while(T--){
memset(g,0,sizeof(g));
memset(dp,0,sizeof(dp));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
g[x][y]=true;
}
dp[0][0]=1;
dp[1][0]=1;
int mx=(1<<n)-1;
int w=0,q=1;
for(int i=1;i<=n;i++){
w=q;q=w^1;
for(int j=mx;j>=0;j--){
if(dp[q][j]==0) continue;
for(int k=1;k<=n;k++){
if(g[i][k]) continue;
if(j&(1<<(k-1))) continue;
dp[w][j|(1<<(k-1))]+=dp[q][j];
}
}
}
cout<<"Case "<<++cas<<": "<<fact[n]-dp[w][mx]<<endl;
}
return 0;
}