Permutation HUD-3811 (状态压缩)

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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值