HDU 4917 Permutation 拓扑排序的计数

题意:

  一个有n个数的排列,给你一些位置上数字的大小关系。求合法的排列有多少种。

思路:

  数字的大小关系可以看做是一条有向边,这样以每个位置当点,就可以把整个排列当做一张有向图。而且题目保证有解,所以只一张有向无环图。这样子,我们就可以把排列计数的问题转化为一个图的拓扑排序计数问题。

  拓扑排序的做法可以参见ZJU1346

  因为题目中点的数量比较多,所以无法直接用状压DP。 但是题目中的边数较少,所以不是联通的,而一个连通块的点不超过21个,而且不同连通块之间可以看做相互独立的。所以我们可以对于每个连通块分别求解,然后利用排列组合的知识,把他们组合起来。这样就可以得到最终解了。

代码:

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <cstdlib>
  5 #include <cmath>
  6 #include <algorithm>
  7 #include <string>
  8 #include <queue>
  9 #include <stack>
 10 #include <vector>
 11 #include <map>
 12 #include <set>
 13 #include <functional>
 14 #include <time.h>
 15 
 16 using namespace std;
 17 
 18 typedef __int64 ll;
 19 
 20 const int INF = 1<<30;
 21 const int MAXM = 21;
 22 const int MAXN = 50;
 23 const ll MOD = (ll) 1e9+7;
 24 
 25 ll dp[1<<MAXM]; //
 26 ll combine[MAXN][MAXN]; //组合数
 27 int Matrix[MAXN][MAXN]; //邻接矩阵,1表示正向边,-1表示反向边
 28 int tmp[MAXM], pre[MAXM], num; //求每个连通块的拓扑序用
 29 bool vis[MAXN]; //求连通块用
 30 int n, m, tot;
 31 ll ans;
 32 
 33 void dfs(int u) {
 34     int u2 = num; //当前点的标号
 35     tmp[num++] = u; //把这个点加入集合
 36     vis[u] = true;
 37 
 38     int v;
 39     for (int i = 1; i <= n; i++) if (Matrix[u][i]) { //如果有边
 40         if (!vis[i]) dfs(i); //如果没找过先找后继
 41         if (1==Matrix[u][i]) {
 42             for (v = 0; v < num; v++) if (tmp[v]==i) {
 43                 pre[v] |= (1<<u2); //把这个点加入后继的前驱集合
 44             }
 45         }
 46     }
 47 }
 48 
 49 ll calc() { //计算某个连通块的拓扑数量
 50     memset(dp, 0, sizeof(dp));
 51     dp[0] = 1;
 52 
 53     for (int S = 0; S < (1<<num); S++) if (dp[S]>0)
 54         for (int i = 0; i < num; i++) if (((S&pre[i])==pre[i]) && !(S&(1<<i))) {
 55             dp[S|(1<<i)] = (dp[S|(1<<i)]+dp[S])%MOD;
 56         }
 57 
 58     return dp[(1<<num)-1];
 59 }
 60 
 61 void solve() {
 62     memset(vis, false, sizeof(vis));
 63     ans = 1;
 64     tot = n;
 65     for (int i = 1; i <= n; i++) if (!vis[i]) { //搜连通块
 66         memset(pre, 0, sizeof(pre)); num = 0;
 67         dfs(i);
 68         if (num<2) //只有一个或者两个点的情况,拓扑序时确定的
 69             ans = (((num*combine[tot][num])%MOD)*ans)%MOD;
 70         else
 71             ans = (((calc()*combine[tot][num])%MOD)*ans)%MOD;
 72 
 73         tot -= num;
 74     }
 75 
 76     printf("%I64d
", ans);
 77 }
 78 
 79 int main() {
 80     #ifdef Phantom01
 81         freopen("HDU4917.in", "r", stdin);
 82 //        freopen("HDU4917.out", "w", stdout);
 83     #endif //Phantom01
 84 
 85     for (int i = 0; i < MAXN; i++) { //预处理组合数(杨辉三角)
 86         combine[i][0] = 1;
 87         for (int j = 1; j <= i; j++)
 88             combine[i][j] = (combine[i-1][j-1] + combine[i-1][j])%MOD;
 89     }
 90 
 91     while (scanf("%d%d", &n, &m)!=EOF) {
 92         memset(Matrix, 0, sizeof(Matrix));
 93         int u, v;
 94         for (int i = 0; i < m; i++) {
 95             scanf("%d%d", &u, &v);
 96             Matrix[u][v] = 1;
 97             Matrix[v][u] = -1;
 98         }
 99         solve();
100     }
101 
102     return 0;
103 }
HDU4917

  写的时候,莫名其妙的wa了一发,要数据来对拍才发现是中间结果溢出了……所以,小心的调整运算顺序和适当的加括号很有必要。

原文地址:https://www.cnblogs.com/Phantom01/p/3899191.html