NOIP提高模拟-20181017-T2-管道(状压DP)

写在前面

本来写了一个DFS强行暴力一发,然而DFS是错的,我写的DFS和标算的方法大相径庭,愉快爆零。

Solution

20~40pts做法
直接暴搜即可。

60~100pts做法
搜索+记忆化。

100pts做法详细解析
dpi,sta表示当前在i点,已访问过的点集为sta时,将剩余所有能访问得到的点访问完后的总方案数。然而,似乎无法直接维护dp数组,因为在搜索的时候,剩下的所有能访问到的点的状态还未被计算,而在一张图上,又不能用循环进行状态转移。所以说我们需要借助一个辅助数组f
fi,sta表示当前在i点,已访问过的点集为sta时,访问剩余所有能访问得到的点后的状态,有了这个数组,我们就可以在计算dp数组时利用f数组已经计算出来的状态,来更新dp数组。
又因为是无向图,所以dfs一个点以后会访问完所有与它联通的点才会回到这个点,那么我们可以得到转移方程:
dpi,sta=jdpj,sta2jdpi,stafj,sta
对于f,可以直接dfs求,时间复杂度O(n22n)
Talk is Cheap, Show You the Code:

#include<bits/stdc++.h>
using namespace std;
const int N=20;
const int mod=998244353;
const int M=450;
int read(){
	int sum=0,neg=1;
	char c=getchar();
	while(c>'9'||c<'0'&&c!='-') c=getchar();
	if(c=='-') neg=-1,c=getchar();
	while(c>='0'&&c<='9') sum=(sum<<1)+(sum<<3)+c-'0',c=getchar();
	return sum*neg;
}
int n,m;
struct Edge{
	int u,v;
}e[M<<1];
int first[M],nxt[M],cnt=0;
void AddEdge(int u,int v){
	e[++cnt].u=u;e[cnt].v=v;
	nxt[cnt]=first[u]; first[u]=cnt;
}
int f[1<<N][N];
int dfs1(int sta,int u){
	if(f[sta][u]) return f[sta][u];
	f[sta][u]=sta;
	for(int i=first[u];i;i=nxt[i]){
		int v=e[i].v;
		if(!(sta&(1<<v))) f[sta][u]|=dfs1(sta|(1<<v),v);//没有访问过
			
	}
	return f[sta][u];
}
long long dp[1<<N][N];
int dfs(int sta,int u){
	if(f[sta][u]==sta) return 1;
	if(dp[sta][u]!=-1) return dp[sta][u];
	dp[sta][u]=0;
	for(int i=first[u];i;i=nxt[i]){
		int v=e[i].v;
		if(sta&(1<<v)) continue;
		int x=dfs(sta|(1<<v),v);
		int y=dfs(f[sta|(1<<v)][v],u);
		dp[sta][u]+=(long long)x*y%mod;
	}
	return dp[sta][u]%=mod;
}
int main(){
	n=read(); m=read();
	for(int i=1;i<=m;i++){
		int u,v;
		u=read()-1; v=read()-1;
		AddEdge(u,v);
		AddEdge(v,u);
	}
	for(int i=0;i<(1<<n);i++)
		for(int j=0;j<n;j++)
			if(i&(1<<j)) f[i][j]=dfs1(i,j);//i表示全图访问状态的一个子集
	memset(dp,-1,sizeof(dp));
	long long ans=0;
	for(int i=0;i<n;i++) ans+=dfs(1<<i,i);
	printf("%lld\n",ans%mod);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值