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\n", 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了一发,要数据来对拍才发现是中间结果溢出了……所以,小心的调整运算顺序和适当的加括号很有必要。

HDU 4917 Permutation 拓扑排序的计数

时间: 2024-10-22 06:54:19

HDU 4917 Permutation 拓扑排序的计数的相关文章

HDU 4917 Permutation

题意: 一个序列p1.p2.p3--pn是由1.2.3--n这些数字组成的  现给出一些条件pi<pj  问满足所有条件的排列的个数 思路: 很容易想到用一条有向的线连接所有的pi和pj  那么就构成了有向无环图(题中说有解所以无环) 又因为pi各不相同  那么题目就变成了有向无环图的拓扑排序的种类数 题目中边数较少  所以可能出现不连通情况  我们先讨论一个连通集合内拓扑排序的种类数 题目中m较小  可以利用状压后的记忆化搜索解决 现在考虑如果知道了A和B两个集合各自的种类数  如果把它们合起

hdu 2647 Reward (拓扑排序分层)

Reward Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 3815    Accepted Submission(s): 1162 Problem Description Dandelion's uncle is a boss of a factory. As the spring festival is coming , he wa

ZJU 1346 Comparing Your Heroes 状态压缩DP 拓扑排序的计数

做多校的时候遇见一个求拓扑排序数量的题,就顺便来写了一下. 题意: 你有个朋友是KOF的狂热粉丝,他有一个对其中英雄的强弱比较,让你根据这些比较关系来给这些英雄排名.问一共有多少种排名方式. 思路: 用dp[S]记录当前状态的数量. S表示拓扑排序中当前阶段已经被排序的点的集合.然后就可以枚举当前排序的点,转移的条件是这个点的所有前驱都被排序,而且这个点没被排序.然后转移就好了,最终状态就是所有点都完成排序. 代码: 1 #include <iostream> 2 #include <c

hdu 4857 逃生 拓扑排序+优先队列,逆向处理

hdu4857 逃生 题目是求拓扑排序,但不是按照字典序最小输出,而是要使较小的数排在最前面. 一开始的错误思路:给每个点确定一个优先级(该点所能到达的最小的点),然后用拓扑排序+优先对列正向处理,正向输出.这是错误的,如下样例: 1 5 4 5 2 4 3 2 1 3 1 正确的解法:是反向建边,点大的优先级高,用拓扑排序+优先队列,逆向输出序列即可. 根据每对限制,可确定拓扑序列,但此时的拓扑序列可能有多个(没有之间关系的点的顺序不定).本题要求较小的点排到前面,则可确定序列. (1)如果点

hdu 5438 Ponds 拓扑排序

Ponds Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1001&cid=621 Description Betty owns a lot of ponds, some of them are connected with other ponds by pipes, and there will not be more than one

ACM: hdu 2647 Reward -拓扑排序

hdu 2647 Reward Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Description Dandelion's uncle is a boss of a factory. As the spring festival is coming , he wants to distribute rewards to his workers. Now he has a trouble

HDU 2647 Reward(拓扑排序)

Problem Description Dandelion's uncle is a boss of a factory. As the spring festival is coming , he wants to distribute rewards to his workers. Now he has a trouble about how to distribute the rewards. The workers will compare their rewards ,and some

hdu 2647 Reward 拓扑排序。

Reward Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 4599    Accepted Submission(s): 1400 Problem Description Dandelion's uncle is a boss of a factory. As the spring festival is coming , he w

hdu 4857 逃生 拓扑排序+PQ,剥层分析

pid=4857">hdu4857 逃生 题目是求拓扑排序,但不是依照字典序最小输出,而是要使较小的数排在最前面. 一開始的错误思路:给每一个点确定一个优先级(该点所能到达的最小的点).然后用拓扑排序+优先对列正向处理,正向输出.这是错误的.例如以下例子: 1 5 4 5 2 4 3 2 1 3 1 正确的解法:是反向建边.点大的优先级高,用拓扑排序+优先队列,逆向输出序列就可以. 依据每对限制,可确定拓扑序列,但此时的拓扑序列可能有多个(没有之间关系的点的顺序不定).本题要求较小的点排到