【CSP 2019】城市规划

Posted by WHZ0325 on 2022-08-25, Viewed times

题目描述

给定一颗 $n$ 个节点的树,其中给出 $m$ 个节点可选,要求从这 $m$ 个节点中选出 $k$ 个节点使得已选节点两两之间的距离和最小。

$n\le 5\times 10^4$,$m\le 10^4$,$k\le 100$。

算法分析

树上 0/1 背包动态规划模版题,设 $f[i][j]$ 为访问节点 $i$ 时在其子树(不包括节点 $i$)中选择 $j$ 个节点时子树中所有边给予的最小贡献。若包含根节点的子树中选择了 $kk$ 个节点那么该子树根节点到其父节点的边所给予的贡献为 $w\times kk\times (k-kk)$($w$ 为边权)。

注意计算顺序,DFS 后递推即可,时间长不写了第一次错写成记忆化搜索的完全背包了(汗)……

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 50005
#define K 105
typedef long long int ll;
struct edge {int v,w;};
int imp[N];std::vector<edge> edges[N];
int n,k;ll f[N][K];
void dfs(int x,int fa) {
for(int i=0,sz=edges[x].size();i<sz;++i) {
edge e=edges[x][i];if(e.v==fa) continue;dfs(e.v,x);
for(int j=k;j>0;--j) {
for(int kk=j;kk>=0;--kk) {
if(kk) f[x][j]=std::min(f[x][j],f[e.v][kk]+f[x][j-kk]+((k-kk)?(ll)e.w*kk*(k-kk):0));
if(imp[e.v]&&j>kk) {
f[x][j]=std::min(f[x][j],f[e.v][kk]+f[x][j-kk-1]+(ll)e.w*(kk+1)*(k-kk-1));
}
}
}
}
}
int main() {
int m;scanf("%d%d%d",&n,&m,&k);
while(m--) {int t;scanf("%d",&t);imp[t]=true;}
for(int i=1;i<n;++i) {
int a,b,c;scanf("%d%d%d",&a,&b,&c);
edges[a].push_back((edge){b,c});
edges[b].push_back((edge){a,c});
}
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;++i) f[i][0]=0;
dfs(1,0);ll ans=f[1][k];if(imp[1]&&k) ans=std::min(ans,f[1][k-1]);
printf("%lld\n",ans);
return 0;
}