考虑在无向图上进行 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\) 建立站点,可以这样更新:
- 若在 \(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。
标签:pw,min,题解,TUR,dfs,站点,P3577,节点,dp From: https://www.cnblogs.com/zifanoi/p/18038525