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

  • 相关阅读:
    eclipse导入项目后出现红色叉号
    java.lang.ArithmeticException: Non-terminating decimal expansion
    IDEA 2020 注册码
    最新调查:游戏女性玩家群体达3亿!这意味着什么,嘻嘻......
    Linux安装配置Nginx服务器
    mysql/mariadb 远程登陆
    redis 集群 新增 master 节点 与 slave 挂靠命令
    RedisCluster 集群 之 Ruby 安装
    Linux CentOS 7 关闭防火墙
    springcloud 与 springboot 对应关系
  • 原文地址:https://www.cnblogs.com/Phantom01/p/3899191.html
Copyright © 2011-2022 走看看