IOI 2005 Riv 河流 题解

题目传送门

题目大意: 给出一棵树,每个点有一些木材,除了根节点外(本身就是伐木场),你需要选 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);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/109114300