标签: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 */
hdu 4917Permutation(状态压缩DP),布布扣,bubuko.com
标签:dp
原文地址:http://blog.csdn.net/no__stop/article/details/38531823