hdu 4917Permutation(状态压缩DP)

hdu 4917Permutation(状态压缩DP)

题意:将1~n的n个数排列成序列(n<=40),但有m(m<=20)个限制条件,其中第i个限制条件的表示为ai,bi。表示该序列的第ai的数要小于第bi的。问有多少中排列?保证有解

解法:我们首先可以明确一点,这m个限制条件,所表示的关系会构成若干个DAG(有向无环图,我将其称之为拓扑图)。我们只要将这n个数,填入到拓扑图上,使其满足拓扑关系,那么这样的序列就是可以的。而这若干个拓扑图之间,是不会相互影响的,因而我们可以单独考虑每一个拓扑图。对于单独的一个拓扑图,假设有k个节点(即要放入k个数),随便放入k个数,都可以构成解,而且无论放什么,对于这个拓扑图,能满足条件的放法都是一样多的,因此每一个拓扑图要乘上一个组合数(选k个有多少种选法)。然后我们来考虑某个拓扑图的内部放法。因为m<=20,那么每个拓扑图内最多有21个点。我们用状态压缩dp来解决这个问题。前面我们得知,无论选的k个数是什么,在本拓扑图内合法的排放序列都是一样多的,我们不妨假设这k个位置为0~K-1,我们将k个数字按从大到小往图上放,记dp[m]为,放好了二进制状态为m(二进制1表示相应位表示的节点已填入了数字,0表示未放),dp从小到大递推即可得解。而dp状态转移的条件是,我要填入某一位时,该位的前驱都已选入在m这个状态中了,因此可以先处理拓扑图,得到每一位的前驱。

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
#include<queue>
#define ll __int64
using namespace std ;

vector<int> vec[44] ;
const int mod = 1000000007 ;
int dp[1<<21] ;
int fa[44] , sz[44] , vis[44] ;
int to[44] , id[44] , du[44] ;
int c[44][44] ;

void init () {
    c[0][0] = 1 ;
    for ( int i = 1 ; i < 44 ; i ++ ) {
        c[i][0] = 1 ;
        for ( int j = 1 ; j <= i ; j ++ )
            c[i][j] = ( c[i-1][j] + c[i-1][j-1] ) % mod ;
    }
}

int find ( int a ) {
    return fa[a] == a ? a : fa[a] = find ( fa[a] ) ;
}

struct Point {
    int id , st ;
    Point () {}
    Point (int a , int b):id(a),st(b) {}
} ;

int gao ( int s , int n , int& re) {
    int tot = 0 ;
    queue<Point> Q ;
    for ( int i = 1 ; i <= n ; i ++ ) {
        if ( find (i) == s && du[i] == 0 ) {
            to[tot++] = 0 ;
            Q.push ( Point(i,1<<tot-1) ) ;
        }
    }
    while ( !Q.empty () ) {
        Point u = Q.front () ;
        int st = u.st ;
        int f = u.id ;
        Q.pop () ;
        for ( int i = 0 ; i < vec[f].size () ; i ++ ) {
            int v = vec[f][i] ;
            du[v] -- ;
            id[v] ¦= st ;
            if ( du[v] == 0 ) {
                to[tot++] = id[v] ;
                Q.push ( Point(v,id[v]¦(1<<(tot-1))) ) ;
            }
        }
    }
    ll ret = c[re][tot] ;
 //   printf ( "ret = %I64d\n" , ret ) ;
    re -= tot ;
    dp[0] = 1 ;
    for ( int i = 1 ; i < 1<<tot ; i ++ ) {
        dp[i] = 0 ;
        for ( int j = 0 ; j < tot ; j ++ ) {
            if ( ((i&(1<<j))) ) {
                int st = i ^ (1<<j) ;
                if ( (~st)&to[j] ) continue ;
                dp[i] += dp[i^(1<<j)] ;
                dp[i] %= mod ;
   //             printf ( "i = %d , to[%d] = %d\n" , i , j , to[j] ) ;
   //             printf ( "dp[%d] = %d\n" , i , dp[i] ) ;
            }
        }
    }
//    printf ( "dp[%d] = %d\n" , (1<<tot)-1 , dp[(1<<tot)-1] ) ;
    ret *= dp[(1<<tot)-1] ;
    ret %= mod ;
    return ret ;
}

int main () {
    init () ;
    int n , m ;
    while ( scanf ( "%d%d" , &n , &m ) != EOF ) {
        int re = n ;
        memset ( vis , 0 , sizeof ( vis ) ) ;
        for ( int i = 1 ; i <= n ; i ++ ) {
            vec[i].clear () ;
            fa[i] = i ;
            du[i] = 0 ;
            id[i] = 0 ;
        }
        for ( int i = 1 ; i <= m ; i ++ ) {
            int a , b ;
            scanf ( "%d%d" , &a , &b ) ;
            vec[a].push_back ( b ) ;
            du[b] ++ ;
            int x = find ( a ) , y = find ( b ) ;
            if ( x != y ) fa[x] = y ;
        }
        ll ans = 1 ;
        for ( int i = 1 ; i <= n ; i ++ ) {
            if ( !vis[find(i)] ) {
                ans *= gao (find(i),n,re) ;
                ans %= mod ;
                vis[find(i)] = 1 ;
            }
        }
        printf ( "%I64d\n" , ans ) ;
    }
    return 0 ;
}
/*
3 2
1 2
1 3
4 3
1 2
1 3
1 4
4 4
1 2
1 3
2 4
3 4

6 6
1 2
1 3
3 4
2 4
4 5
4 6

8 9
2 1
3 1
5 1
4 2
6 3
7 5
8 4
8 6
8 7

5 4
3 1
3 2
4 3
5 3
*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值