[CSP-S模拟测试]:巨神兵(状压DP)

题目描述

欧贝利斯克的巨神兵很喜欢有向图,有一天他找到了一张$n$个点$m$条边的有向图。
欧贝利斯克认为一个没有环的有向图是优美的,请问这张图有多少个子图(即选定一个边集)是优美的?答案对$1,000,000,007$取模。


输入格式

第一行两个整数$n$和$m$。
接下来$m$行每行两个整数表示一条有向边。保证无重边无自环。


输出格式

一行一个整数表示答案,对$1,000,000,007$取模。


样例

样例输入:

3 6
1 2
2 1
1 3
3 1
2 3
3 2

样例输出:

25


数据范围与提示

对于$40\%$的数据$n\leqslant 5,m\leqslant 20$;
对于$60\%$的数据$n\leqslant 10$;
对于$80\%$的数据$n\leqslant 15$;
对于$100\%$的数据$n\leqslant 17$。


题解

看到数据范围是$17$的时候,我觉得这场考试我能$AK$;然而,当我看到是边集的时候,我发现半个小时之后机房里听不见一点键盘声……

同样,我们先考虑$60\%$的做法。

先给有向无环图分层,第一层是入度为$0$的点,第二层是删去第一层后度数为$0$的点,类似$topsort$?

然后设$dp[i][j]$表示当前选择的节点集合为$i$,最后一层的节点集合为$j$的方案数。

不妨设$s_1$表示当前选择的节点集合,$s_2$表示最后一层的节点的集合,$s_3$为$s_1$补集的子集,且$s_2$与$s_3$有连边,$s_1\oplus s_2$与$s_3$的连边为$cnt_1$条,$s_2$与$s_3$的连边有$cnt_2$条。

那么,状态转移方程为:

$$dp[s_1|s_3][s_3]=\sum dp[s_1][s_2]\times \prod 2^{cnt_1}\times (2^{cnt_2}-1)$$

这样的时间复杂度是$\Theta(4^n\times m)$的,无论空间还是时间都是不行的。

所以我们考虑减掉第二维,直接枚举当前选择的节点集合$s_1$,然后再枚举其补集的子集$s_2$,设$s_1$与$s_2$之间的连边有$cnt$条。

那么,你可能会列出这样一个状态转移方程:$dp[s_1|s_2]=dp[s_1]\times 2^{cnt}$。

但是你会发现答案会统计多,因为$s_1|s_2$可以由很多$s_1$和$s_2$组成,所以我们还需要容斥,其系数为$(-1)^{sz[s]+1}$。

这时候的时间复杂度为$\Theta(3^nm)$,只能拿到$80$分,还需要优化,有好多优化,我将其优化到了$\Theta(2^n\times n^2)$,这样就可以$AC$这道题了。

时间复杂度:$\Theta(2^n\times n^2)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
int n,m;
bool Map[20][20];
int sz[131073];
int du[65537];
int sum[131073];
int qpow[1000];
int dp[131073];
int main()
{
	scanf("%d%d",&n,&m);
	int maxn=(1<<n);
	dp[0]=qpow[0]=1;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		Map[x-1][y-1]=1;
		qpow[i]=(qpow[i-1]<<1)%mod;
	}
	sz[0]=-1;
	for(int i=1;i<maxn;i++)sz[i]=sz[i>>1]*(i&1?-1:1);
	for(int i=0;i<maxn-1;i++)
	{
		if(!dp[i])continue;
		int t=maxn-1-i;
		memset(du,0,sizeof(du));
		for(int j=0;j<n;j++)
			if(i&(1<<j))
				for(int k=0;k<n;k++)
					du[1<<k]+=Map[j][k];
		sum[0]=0;
		for(int s=(t-1)&t;;s=(s-1)&t)
		{
			int now=t^s,lst=now&-now;
			sum[now]=sum[now-lst]+du[lst];
			dp[i+now]=(dp[i+now]+1LL*sz[now]*qpow[sum[now]]*dp[i])%mod;
			if(!s)break;
		}
	}
	printf("%lld",dp[maxn-1]);
	return 0;
}

rp++

posted @ 2019-10-03 21:35  HEOI-动动  阅读(461)  评论(4编辑  收藏  举报