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

  • 相关阅读:
    Codeforces 1485C Floor and Mod (枚举)
    CodeForces 1195D Submarine in the Rybinsk Sea (算贡献)
    CodeForces 1195C Basketball Exercise (线性DP)
    2021年初寒假训练第24场 B. 庆功会(搜索)
    任务分配(dp)
    开发工具的异常现象
    Telink MESH SDK 如何使用PWM
    Telink BLE MESH PWM波的小结
    [LeetCode] 1586. Binary Search Tree Iterator II
    [LeetCode] 1288. Remove Covered Intervals
  • 原文地址:https://www.cnblogs.com/Phantom01/p/3899191.html
Copyright © 2011-2022 走看看