Codeforces 70E 题解

题意

给你一个图,需要给每个点分配一个信息中心,在点上建立信息中心需要花费 $k$;此外,传输代价为d[点到其中心的距离]。求最小总代价及分配方案。

题解

开始发现dp状态不是很好设计,所以可以考虑dp的性质——无后效性,即无需考虑当前会对未来造成什么影响。

而当前状态是由之前的状态(叶节点的状态转移而来),所以可以设 $f_{u,i}$ 表示 $u$ 子树被分配到 $i$ 的代价。

转移的时候就讨论一下叶节点是否也分配到了 $i$ 即可。

初始状态?

在叶节点时 $f_{u,i}=k+d[dis_{u,i}]$

向上推,如果和某一个儿子使用一样的中心,那么就减去重复的k即可;如果不用一样的中心,那么就选儿子最小代价的方案转移过来。

所以 $f_{u,i}=k+d[dis_{u,i}]+\sum_{(u,v)}min(f_{v,i}-k,f_{v,mn[v]})$ (其中 $mn[v]$ 代表 $f_{v,i}$ 中最小的那个 $i$)

那么就做好啦,输出方案只需要从根开始往下搜,每次是从哪种情况转移来的即可。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,k;
int d[200];
int dis[200][200];
struct edge{
    int to,nxt;
}ed[400];int edcnt=0;
int hd[200];
inline void adde(int u,int v){
    ed[++edcnt]={v,hd[u]};
    hd[u]=edcnt;
}
int dp[200][200],cho[200][200],mn[200];
void dfs(int u,int fa){
    for(int i=1;i<=n;++i)dp[u][i]=k+d[dis[u][i]];
    for(int id=hd[u];id;id=ed[id].nxt){
        int v=ed[id].to;
        if(v==fa)continue;
        dfs(v,u);
        for(int i=1;i<=n;++i){
            dp[u][i]+=min(dp[v][i]-k,dp[v][mn[v]]);
        }
    }
    for(int i=1;i<=n;++i){
        if(!mn[u]||dp[u][mn[u]]>dp[u][i])mn[u]=i;
    }
}
int ans[200];
void dfs2(int u,int i,int fa){
    for(int id=hd[u];id;id=ed[id].nxt){
        int v=ed[id].to;
        if(v==fa)continue;
        if(dp[v][i]-k<dp[v][mn[v]]){
            ans[v]=i;
            dfs2(v,i,u);
        }else{
            ans[v]=mn[v];
            dfs2(v,mn[v],u);
        }
    }
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<n;++i)scanf("%d",&d[i]);
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)dis[i][j]=(i==j?0:10000);
    for(int i=1,u,v;i<n;++i){
        scanf("%d%d",&u,&v);
        adde(u,v);adde(v,u);
        dis[u][v]=dis[v][u]=1;
    }
    for(int k=1;k<=n;++k)
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    dfs(1,1);
    cout<<dp[1][mn[1]]<<endl;
    dfs2(1,mn[1],1);
    ans[1]=mn[1];
    for(int i=1;i<=n;++i)printf("%d ",ans[i]);
}