[洛谷P3569] POI2014 KAR

问题描述

给定一个n个点,m条边的无向图,其中你在第i个点建立旅游站点的费用为Ci。在这张图中,任意两点间不存在节点数超过10的简单路径。请找到一种费用最小的建立旅游站点的方案,使得每个点要么建立了旅游站点,要么与它有边直接相连的点里至少有一个点建立了旅游站点。

解析

对于这类问题,不妨对原图的每个连通块建出 DFS 树,那么原问题就转化为了树上点覆盖问题。由题目的性质,这棵树的深度不会超过10。

我们用一个3进制数表示一个点的状态:0表示这个点被选择,1表示这个点没有被选也没有被覆盖,2表示这个点没有被选但是被覆盖了。设 \(f_{u,d,s}\) 表示当前在点 \(u\) ,深度为 \(d\)\(u\) 的祖先们状态为 \(s\) 时的最小代价。转移时先考虑 \(u\) 的返祖边,然后继续DP,再用子树的状态更新 \(f_{u,d}\) 。在实际实现中 \(u\) 这一维可以省略。

设连通块的 DFS 树根节点为 \(root\),那么答案为 \(\sum \min(f_{root,0,0},f_{root,0,2})\)

代码

#include <iostream>
#include <cstdio>
#define N 20002
#define M 25002
#define S 60000
using namespace std;
const int inf=1<<30;
int head[N],ver[M*2],nxt[M*2],l;
int n,m,i,w[N],c[N],f[12][N],pw[12],a[N],dep[N],ans;
bool vis[N];
int read()
{
	char c=getchar();
	int w=0;
	while(c<'0'||c>'9') c=getchar();
	while(c<='9'&&c>='0'){
		w=w*10+c-'0';
		c=getchar();
	}
	return w;
}
void insert(int x,int y)
{
	l++;
	ver[l]=y;
	nxt[l]=head[x];
	head[x]=l;
}
void dfs(int x,int pre)
{
	vis[x]=1;
	int cnt=0,d=dep[x];
	if(d==0) f[0][0]=w[x],f[0][1]=0,f[0][2]=inf;
	else{
		for(int i=head[x];i;i=nxt[i]){
			int y=ver[i];
			if(vis[y]&&dep[y]<d) a[++cnt]=dep[y];
		}
		for(int s=0;s<pw[d+1];s++) f[d][s]=inf;
		for(int s=0;s<pw[d];s++){
			int s1=s,op=1;
			for(int i=1;i<=cnt;i++){
				if(s/pw[a[i]]%3==0) op=2;
				else if(s/pw[a[i]]%3==1) s1+=pw[a[i]];
			}
			f[d][s+op*pw[d]]=min(f[d][s+op*pw[d]],f[d-1][s]);
			f[d][s1]=min(f[d][s1],f[d-1][s]+w[x]);
		}
	}
	for(int i=head[x];i;i=nxt[i]){
		int y=ver[i];
		if(!vis[y]){
			dep[y]=dep[x]+1;
			dfs(y,x);
			for(int s=0;s<pw[d+1];s++) f[d][s]=min(f[d+1][s],f[d+1][s+2*pw[d+1]]);
		}
	}
}
int main()
{
	n=read();m=read();
	for(i=1;i<=n;i++) w[i]=read();
	for(i=1;i<=m;i++){
		int u=read(),v=read();
		insert(u,v);insert(v,u);
	}
	for(i=pw[0]=1;i<=10;i++) pw[i]=pw[i-1]*3;
	for(i=1;i<=n;i++){
		if(!vis[i]){
			dfs(i,0);
			ans+=min(f[0][0],f[0][2]);
		}
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2020-11-01 23:07  CJlzf  阅读(96)  评论(0编辑  收藏  举报