BZOJ3836 [Poi2014]Tourism

该博客介绍了如何解决一个图论问题,即在给定无向图中寻找最小费用的旅游站点设立方案。题目要求每个点要么自身建立站点,要么与相邻已建立站点的点直接相连。博主通过DFS搜索树和树DP的方法进行了求解,详细阐述了两种状态转移情况,分别是节点未选和节点被选,并提供了相应的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简要题意:
给定一个n个点,m条边的无向图,其中你在第i个点建立旅游站点的费用为Ci。在这张图中,任意两点间不存在节点数超过10的简单路径。请找到一种费用最小的建立旅游站点的方案,使得每个点要么建立了旅游站点,要么与它有边直接相连的点里至少有一个点建立了旅游站点。
题解:
根据dfs搜索树进行树DP。
对一个点的影响不仅有它的儿子,还有它的祖先。
对每一个独立的联通块DP,令f[i][s]表示在dfs搜索树中深度为i的点,其祖先的状态为s。其中s是一个三进制数。对于s中每一位上,0表示该点选了,1表示该点未选且这个点不满足要求,2表示该点未选但这个点满足要求(即它的邻接点选了)。
第一种转移:节点u不选。
访问节点u,其深度为dep时,先找出它的邻接点且为它的祖先。
第一种转移:节点u不选。
若其中有一个点选了,则
f[dep][s+2∗pow[dep]]=min(f[dep][s+2∗pow[dep]],f[dep−1][s]);
否则f[dep][s+pow[dep]]=min(f[dep][s+pow[dep]],f[dep−1][s]+val[u]);
第二种转移:节点u选了。
那么将s中为1的点改成2即可。
对于它的儿子,直接用min(f[dep+1][s],f[dep+1][s+2∗pow[dep+1]])来更新f[dep][s]即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=20005,M=50010,K=59050,INF=1000000000;
int read(){
	int x=0,f=1; char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1; ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}
struct Edg{
	int nxt,poi;
}e[M];
int c[N],f[11][K],deep[N],pw[N],first[N],l=0,st[N];
bool vis[N];
void addedge(int u,int v){
	l++;
	e[l].nxt=first[u];
	e[l].poi=v;
	first[u]=l;
}
void dfs(int u){
	vis[u]=1;
	int mx=pw[deep[u]]-1,d=deep[u],top=0;
	for (int i=0;i<pw[deep[u]+1];i++) f[d][i]=INF;
	if (!d) f[0][0]=0,f[0][1]=INF,f[0][2]=c[u];
	for (int p=first[u];p;p=e[p].nxt){
		int v=e[p].poi; if (vis[v]) st[++top]=v;
    }
    for (int s=0;s<=mx;s++){
    	int e=s+2*pw[d],t=0;
    	for (int i=1;i<=top;i++){
    		int dv=deep[st[i]];
    		int sv=s/pw[dv]%3;
    		if (sv==2) t=1;
    		if (!sv) e+=pw[dv];
		}
		f[d][s+t*pw[d]]=min(f[d][s+t*pw[d]],f[d-1][s]);
		f[d][e]=min(f[d][e],f[d-1][s]+c[u]);
	}
	for (int p=first[u];p;p=e[p].nxt){
		int v=e[p].poi;
		if (vis[v]) continue;
		deep[v]=deep[u]+1; dfs(v);
		for (int s=0;s<pw[d+1];s++){
			f[d][s]=min(f[d+1][s+pw[d+1]],f[d+1][s+2*pw[d+1]]);
		}
	}
}
int main(){
	int n=read(),m=read();
	for (int i=1;i<=n;i++) c[i]=read();
	for (int i=1;i<=m;i++){
		int u=read(),v=read();
		addedge(v,u); addedge(u,v);
	}
	pw[0]=1; 
	for (int i=1;i<=13;i++) pw[i]=pw[i-1]*3;
	int ans=0;
	for (int i=1;i<=n;i++){
		if (vis[i]) continue;
		dfs(i); 
		ans+=min(f[0][1],f[0][2]);
	}
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值