P3577 [POI2014]TUR-Tourism 题解

考虑在无向图上进行 dfs,可以得到很多棵 dfs 树(因为图不一定连通),这些树形成了一个森林。

然后由任意两点间不存在节点数超过 \(10\) 的简单路径这个限制可以得出这些树的深度都不超过 \(10\),然后可以想到树上状压 dp。

有一个重要的性质,就是无向图 dfs 树上的非树边,一定是回边,所以不存在横叉边。这也是我们状压这些节点的父亲状态的原因。

对于一个节点,我们用一个数(\(0,1,2\))表示这个节点的状态:

  • \(0\) 表示在这个节点上没有建立站点 且 所有已经访问过的且跟这个节点相邻的节点都没有建立站点。

  • \(1\) 表示 在这个节点没有建立站点 但是 存在一个已经访问过的且跟这个节点相邻的节点建立了站点。

  • \(2\) 表示在这个节点上有建立站点。

下面就是动态规划。

我们用 \(dp[i][j]\) 记录一个最小费用。其中 \(j\) 在三进制表示下从低到高的每一位分别表示从根节点到节点 \(i\) 的路径上的每一个节点的状态。

\(dp[i][j]\) 表示现在从根节点到节点 \(i\) 的路径的状态是 \(j\),在所有已经访问过的节点里,除了从根节点到节点 \(i\) 的路径上的节点以外的所有节点都已经满足了题目中的条件,最小费用是多少。

在 dfs 的过程中当访问到一个节点 \(x\) 时,若 \(x\) 是根节点,则 \(dp[x][0]=0,dp[x][1]=\inf,dp[x][2]=c[x]\),否则我们用 \(fa\) 表示 \(x\) 的父亲,\(d_x\) 表示 \(x\) 的深度(根节点深度为 \(0\)),我们枚举每一个 \(dp[fa][i]\)\(x\) 进行转移(也就是刷表法)。

我们先枚举每一个跟 \(x\) 相邻且已经访问过的节点 \(y\)(由前面的性质可以知道这个节点一定是 \(x\) 的祖先),求出两个值 \(S\)\(T\),初始化 \(T=0,S=i\)

  • \(y\) 的状态是 \(2\),则令 \(T=1\)

  • \(y\) 的状态是 \(0\),则令 \(S=S+3^{d_y}\)

下面考虑是否在 \(x\) 建立站点:

  • 若不在 \(x\) 建立站点,可以这样更新:

\[dp[x][i+T\times3^{d_x}]=\min(dp[x][i+T\times3^{d_x}],dp[fa][i]) \]

  • 若在 \(x\) 建立站点,可以这样更新:

\[dp[x][S+2\times3^{d_x}]=\min(dp[x][S+2\times3^{d_x}],dp[fa][i]+c[x]) \]

从父亲转移过了以后下面就继续 dfs,每访问一个 \(x\) 的儿子 \(y\),就会增加很多已经访问过的节点,所以这时候 \(x\)\(dp\) 状态就不对了,我们要用 \(y\) 来更新 \(x\)(因为 \(y\)\(dp\) 状态是正确的)。

对于每一个 \(dp[x][i]\),我们令:

\[dp[x][i]=\min(dp[y][i+3^{d_y}],dp[y][i+2\times 3^{d_y}]) \]

\(\min\) 里的两个值分别代表在 \(y\) 不建立和建立站点的费用。

最终的答案就是每一棵树的 \(\min(dp[root][1],dp[root][2])\) 的和,这里 \(root\) 表示根节点。

到这里差不多就结束了,但是你会发现内存太大了。

由于每一个节点的状态只跟它的父亲和儿子有关,我们可以用这个节点的深度表示这个节点,具体的方法详见代码。

时间复杂度 \(O(3^{10}(n+m))\)

代码:

#include<bits/stdc++.h>
#define mxn 20003
#define pb push_back
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
using namespace std;
int n,m,ans,pw[13],ds[mxn],c[mxn],dp[13][59049];
vector<int>g[mxn];
bool v[mxn];
void dfs(int x,int d){
	v[x]=1,ds[x]=d;
	if(!d){
		dp[0][0]=0;
		dp[0][1]=1e9;
		dp[0][2]=c[x];
	}else{
		rept(i,0,pw[d+1])dp[d][i]=1e9;
		rept(i,0,pw[d]){
			int t=0,s=i;
			for(int j:g[x])if(v[j]){
				if(i/pw[ds[j]]%3==2)t=1;
				else if(i/pw[ds[j]]%3==0)s+=pw[ds[j]];
			}
			dp[d][i+t*pw[d]]=min(dp[d][i+t*pw[d]],dp[d-1][i]);
			dp[d][s+2*pw[d]]=min(dp[d][s+2*pw[d]],dp[d-1][i]+c[x]);
		}
	}
	for(int i:g[x])if(!v[i]){
		dfs(i,d+1);
		rept(j,0,pw[d+1])dp[d][j]=min(dp[d+1][j+pw[d+1]],dp[d+1][j+pw[d+1]*2]);
	}
}
signed main(){
	pw[0]=1;
	rep(i,1,11)pw[i]=pw[i-1]*3;
	scanf("%d%d",&n,&m);
	rep(i,1,n)scanf("%d",&c[i]);
	for(int i=0,x,y;i<m;++i){
		scanf("%d%d",&x,&y);
		g[x].pb(y),g[y].pb(x);
	}
	rep(i,1,n)if(!v[i]){
		dfs(i,0);
		ans+=min(dp[0][1],dp[0][2]);
	}
	cout<<ans;
	return 0;
}

最后提醒:记得开 O2。

posted @ 2024-02-27 22:05  zifanwang  阅读(10)  评论(0编辑  收藏  举报