UPC12770 小L的有向图 拓扑序个数

问题 H: 小L的有向图

时间限制: 1 Sec  内存限制: 128 MB
提交: 19  解决: 10
[提交] [状态] [命题人:admin]

题目描述

小L有一个n个点m条边有向图,定义一个图的价值为合法的拓扑序个数。
一天,小L想知道她的这个图的价值,小D想烤烤小L,她对图施了魔法,图中的每条边都有可能消失,小L想知道所有可能的情况的图的价值和,即2^m种图的价值和,答案对998244353取模。

 

输入

第一行两个整数n,m,表示图的点数和边数。
接下来m行每行两个数x,y,表示一条x到y的有向边。

 

输出

一行一个整数,表示答案对998244353取模的结果。

 

样例输入

复制样例数据

3 3
1 2
2 3
3 1

样例输出

18

 

提示

对于前20%的测试数据,n≤9。
对于另外20%的测试数据,m≤20。
对于前60%的测试数据,n≤13。
对于前90%的测试数据,n≤20。
对于前100%的测试数据,n≤22,m≤n*(n-1)。
数据保证没有重边,自环(x到y的边和y到x的边不算重边)。

思路:

首先求拓扑序个数就不会,太菜了

拓扑排序中当子节点排好序后,就可以得出父节点排序,即父节点的状态可以从子节点转移

假设一种状态s,二进制位为1的则表明该点已经排好序

例如:s=6时,化为二进制s=110,表示第2、3个点已经排好序了

son[ i ]表示i可以转移的状态,即在有向图中i指向的边

ans[ i ]表示在i状态下的个数

枚举所有的状态,寻找每种状态下二进制为0的点i,如果这个点可以转移的状态是当前状态的子状态,就可以把当前状态转移到

i位是1的状态下

回到这个题,边可消失,那么对每一个边都有存在和不存在两种可能,子节点的贡献也翻倍

状态中每个节点贡献翻倍,t个节点,则为2^t

代码:

#include 

using namespace std;
typedef long long ll;
const int maxn = (1 << 22) + 100;
const int mod = 998244353;
int cnt[maxn], f[30];
int son[30];
ll ans[maxn];

int main() {
    int n, m, x, y;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &x, &y);
        son[x] = son[x] | (1 << (y - 1));//x可以转移的状态,即x指向的边
    }
    int all = (1 << n);
    for (int i = 1; i < all; ++i) cnt[i] = cnt[(i >> 1)] + (i & 1); //统计i二进制下几个1
    f[0] = 1;
    for (int i = 1; i <= n; i++)
        f[i] = f[i - 1] << 1;  //2^i
    ans[0] = 1;
    for (int s = 0; s < all; s++) {
        if (ans[s] > 0) {
            for (int i = 1; i <= n; i++) {
                if ((s & (1 << (i - 1))) == 0)//寻找二进制下为0的状态,
                    ans[(s | (1 << (i - 1)))] =
                            (ans[(s | (1 << (i - 1)))] + ans[s] % mod * f[cnt[(s & son[i])]] % mod) %
                            mod;//每个边可以消失,所以每个边贡献*2,找到子状态里已经确定的边的个数,即1的个数t,求出2^t
            }
        }
    }
    printf("%lld\n", ans[all - 1]);
    return 0;
}

再贴一个正常求拓扑序个数的板子

代码:

int cnt[maxn], f[30];
int son[30];
int ans[maxn];

int main() {
    int n, m, x, y;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &x, &y);
        son[x] = son[x] | (1 << (y - 1));//x可以转移的状态,即x指向的边
    }
    ans[0] = 1;
    int all = (1 << n);
    for (int s = 0; s < all; s++) {
        if (ans[s] > 0) {
            for (int i = 1; i <= n; i++) {
                if ((s & (1 << (i - 1))) == 0 && (s & son[s] == son[s]))
                    ans[(s | (1 << (i - 1)))] += ans[s];
            }
        }
    }
    return 0;
}

 

你可能感兴趣的:(ACM)