cf 70e Information Reform
给出一棵 \(n\) 个节点的树. 要求在树上将一些节点涂成黑点,涂成黑点的代价为 \(k\) . 对于每一种染色的方法,白点到离它最近的黑点的距离为 \(len\) ,需要加上所有白点的 \(d_{len}\). 问最小的代价是多少,要求输出方案.
\(1\leq n\leq 180,1\leq k\leq 100000\)
\(d_i\leq d_{i+1},0\leq d_i\leq 10^5\).
sol 1
\(dp(i,j)\) 表示第 \(i\) 个节点,离节点 \(i\) 最近的节点为 \(j\) 的方案数.
观察可以发现一个性质,如果离节点 \(x\) 最近的节点在子树 \(x\) 外,为节点 \(y\),离 \(par(x)\) 最近的节点必定为 \(y\).
分为两种情况,
- \(y\) 在 \(x\) 子树外,\(dist(par(x),y)=dist(x,y)-1\) ,离 \(par(x)\) 最近的点必定为 \(y\).
- \(y\) 在 \(x\) 子树内,\(dist(par(x),y)=dist(x,y)-l,l>0\),离 \(par(x)\) 最近的点必定为 \(y\).
根据上面的结论可以得到\(dp(i,j)\) 的转移
\(dp(i,j)=\sum\limits_{x\in son(i)}\min\limits_{y\in tree(x)}(dp(x,y),dp(x,j)-k)+d_{dist(i,j)}+k\)
减去 \(k\) ,是因为在计算 \(dp(x,j)\) 时计算过 \(k\) , 此时不用再多设置一个,减去 \(k\).
可以预处理出 \(\min\limits_{y\in tree(x)}dp(x,y)\) . 转移就是 \(O(1)\) 的.
输出方案的时候可以比较 \(\min\limits_{y\in tree(x)} dp(x,y)\) 与 \(dp(x,j)-k\) 的大小,选择转移的方向即可.
时间复杂度: \(O(n^2+n^3)\) \(n^3\) 是 floyd 的时间复杂度,dp 是 \(O(n^2)\) 的.
空间复杂度: \(O(n^2)\)
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9+10;
int n,k;
int d[200],dist[200][200];
int dp[200][200],mp[200];
int l[200],r[200],id=0;
bool edge[200][200];
int p[200];
void floyd(){
for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(i!=j)dist[i][j]=inf;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(edge[i][j])dist[i][j]=1;
for(int k=0;k<n;k++)for(int i=0;i<n;i++)for(int j=0;j<n;j++)
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
}
void get_lr(int x,int fa){
l[x]=id++;
for(int i=0;i<n;i++){
if(i==fa||!edge[x][i])continue;
get_lr(i,x);
}
r[x]=id++;
}
void dfs(int x,int fa){
for(int i=0;i<n;i++){
if(i==fa||!edge[x][i])continue;
dfs(i,x);
int mv=inf;
for(int j=0;j<n;j++){
if(l[i]<=l[j]&&r[j]<=r[i]){
if(mv>dp[i][j]){
mv=dp[i][j];
mp[i]=j;
}
}
}
}
for(int pos=0;pos<n;pos++){
for(int i=0;i<n;i++){
if(i==fa||!edge[x][i])continue;
if(dp[i][mp[i]]<=dp[i][pos]-k)dp[x][pos]+=dp[i][mp[i]];
else dp[x][pos]+=dp[i][pos]-k;
}
dp[x][pos]+=d[dist[x][pos]]+k;
}
}
void get_ans(int x,int pos,int fa){
for(int i=0;i<n;i++){
if(i==fa||!edge[x][i])continue;
if(dp[i][mp[i]]<=dp[i][pos]-k)p[i]=mp[i];
else p[i]=pos;
get_ans(i,p[i],x);
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<n;i++)cin>>d[i];
for(int i=0;i+1<n;i++){
int u,v;
cin>>u>>v;
u--;v--;
edge[u][v]=edge[v][u]=true;
}
floyd();
memset(mp,-1,sizeof(mp));
get_lr(0,-1);
dfs(0,-1);
int pos=-1,ans=inf;
for(int i=0;i<n;i++){
if(dp[0][i]<ans){
ans=dp[0][i];
pos=i;
}
}
p[0]=pos;
get_ans(0,pos,-1);
cout<<ans<<endl;
for(int i=0;i<n;i++)cout<<p[i]+1<<" ";cout<<endl;
return 0;
}
/*inline? ll or int? size? min max?*/
sol 2 (from rng_58)
\(dp(i,j)\) 表示节点 \(i\) ,距离最近的点在子树外,且距离为 \(j\) 时的最小代价.
\(dp(i,0)\) 表示距离最近的点在子树内的最小代价.
对于 \(dp(i,0)\) 和 \(dp(i,j)\) 的转移是不同的.
\(dp(i,0)\) 需要枚举 \(i\) 子树中从节点 \(x\) 转移而来.
\(dp(i,0)=\min\limits_{x\in tree(i)}(k+\sum\limits_{j\in e(x,i)} d_{dist(x,j)}+\sum\limits_{k\in child(j),k\notin e(x,i) } dp(k,dist(x,j)+1))\)
\(dp(i,j)\) 的转移相对简单.
\(dp(i,j)=\min (dp(i,0),\sum\limits_{k\in child(i)} dp(k,j+1))\).
时间复杂度 : \(O(n^3)\)
空间复杂度 : \(O(n^2)\)
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <deque>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
#include <functional>
#include <utility>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <cstdio>
using namespace std;
#define REP(i,n) for((i)=0;(i)<(int)(n);(i)++)
#define foreach(c,itr) for(__typeof((c).begin()) itr=(c).begin();itr!=(c).end();itr++)
typedef long long ll;
#define INF (1<<29)
int N;
int K,d[200];
vector <int> edge[200],child[200];
int parent[200];
int dp[200][200]; // root, dist
int a[200];
int ans[200];
int make_tree(int p, int x){
int i;
if(p != -1) child[p].push_back(x);
parent[x] = p;
REP(i,edge[x].size()) if(edge[x][i] != p) make_tree(x,edge[x][i]);
}
void dfs(int x){
int y,i,j;
REP(i,child[x].size()) dfs(child[x][i]);
dp[x][0] = INF;
REP(y,N){
int z = y;
while(z != -1 && z != x) z = parent[z];
if(z == -1) continue;
ll tmp = 0;
z = y;
int dist = 0, prev = -1;
while(1){
tmp += ((dist == 0) ? K : d[dist]);
REP(i,child[z].size()) if(child[z][i] != prev) tmp += dp[child[z][i]][min(dist+1,N-1)];
if(z == x) break;
prev = z; z = parent[z]; dist++;
}
if(tmp < dp[x][0]) {a[x] = y; dp[x][0] = tmp;}
}
for(i=1;i<N;i++){
dp[x][i] = dp[x][0];
ll tmp = d[i];
REP(j,child[x].size()) tmp += dp[child[x][j]][min(i+1,N-1)];
if(tmp < dp[x][i]) dp[x][i] = (int)tmp;
}
}
void path(int x, int dist, int center){
int i,j;
if(dp[x][dist] != dp[x][0]){
ans[x] = center;
REP(i,child[x].size()) path(child[x][i],min(dist+1,N-1),center);
return;
}
int y = a[x];
i = y;
while(1){
ans[i] = y;
if(i == x) break;
i = parent[i];
}
int prev = -1;
i = y;
int dist2 = 0;
while(1){
REP(j,child[i].size()) if(child[i][j] != prev) path(child[i][j],dist2+1,y);
if(i == x) break;
prev = i; i = parent[i]; dist2++;
}
}
int main(void){
int i;
cin >> N >> K;
REP(i,N-1) cin >> d[i+1];
REP(i,N-1){
int u,v;
cin >> u >> v; u--; v--;
edge[u].push_back(v);
edge[v].push_back(u);
}
make_tree(-1,0);
dfs(0);
cout << dp[0][0] << endl;
path(0,0,-1);
REP(i,N){
cout << ans[i] + 1;
if(i == N-1) cout << endl; else cout << ' ';
}
return 0;
}

浙公网安备 33010602011771号