標籤: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 __int64using 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 21 21 34 31 21 31 44 41 21 32 43 46 61 21 33 42 44 54 68 92 13 15 14 26 37 58 48 68 75 43 13 24 35 3*/