时间限制: 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;
}