题目大意: 给出一棵树,每个点有一些木材,除了根节点外(本身就是伐木场),你需要选 k k k 个点造伐木场,每个点的木材最后会往根节点运送,如果到达了一个伐木场就停下,你要使总运费最小。
题解
很容易想到 w q s wqs wqs 二分,感觉 O ( n 3 ) O(n^3) O(n3) 的做法既繁琐还跑得慢。
给伐木场二分一个权值,然后设dp状态: f x , i f_{x,i} fx,i 表示 x x x 往上第一个伐木场的深度为 i i i,初值就是自己的木材运上去的运费,转移时在 f y , i f_{y,i} fy,i 和 f y , d e p y f_{y,dep_y} fy,depy 中取 min \min min 即可,以及需要记录个 g x , i g_{x,i} gx,i 表示最优方案用了几个伐木场。
就算不做什么优化,速度也是碾压 n 3 n^3 n3 的 d p dp dp,我下面的几个提交是努力卡常的 n 3 n^3 n3 dp做法:
而这个做法也有卡常的余地,随便卡卡能从 53 m s 53ms 53ms 到 48 m s 48ms 48ms,这还只是随便卡卡。
代码也十分好写:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 110
#define ll long long
int n,k,w[maxn];
struct edge{
int y,z,next;}e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y,int z){
e[++len]=(edge){
y,z,first[x]};first[x]=len;}
ll f[maxn][maxn];int g[maxn][maxn],dis[maxn];
void dfs(int x,int dep,int cost){
for(int j=1;j<dep;j++)f[x][j]=1ll*(dis[dep]-dis[j])*w[x],g[x][j]=0;
if(x==1)f[x][dep]=g[x][dep]=0;
else f[x][dep]=cost,g[x][dep]=1;
for(int i=first[x];i;i=e[i].next){
int y=e[i].y;
dis[dep+1]=dis[dep]+e[i].z;
dfs(y,dep+1,cost);
for(int j=1;j<=dep;j++){
if(f[y][dep+1]<f[y][j])f[x][j]+=f[y][dep+1],g[x][j]+=g[y][dep+1];
else f[x][j]+=f[y][j],g[x][j]+=g[y][j];
}
}
}
int main()
{
scanf("%d %d",&n,&k);n++;
for(int i=2,fa,z;i<=n;i++){
scanf("%d %d %d",&w[i],&fa,&z);
buildroad(fa+1,i,z);
}
int l=0,r=2e9;ll ans=-1;
while(l<=r){
int mid=l+r>>1;dfs(1,1,mid);
if(g[1][1]<=k)ans=f[1][1]-1ll*g[1][1]*mid,r=mid-1;
else l=mid+1;
}
printf("%d",ans);
}