3567. 【GDKOI2014】石油储备计划

64 篇文章 0 订阅
7 篇文章 0 订阅

Description

Input

Output

对于每组数据,输出一个整数,表示达到“平衡”状态所需的最小代价。

Sample Input

2

3

6 1 5

1 2 1

2 3 2

5

4 5 4 3 2

1 3 1

1 2 2

2 4 3

2 5 4

Sample Output

4

4

样例解释:

对于第一组数据,从城市1到城市2运输2桶石油,代价为1*2=2;从城市3往城市2运输1桶石油,代价为2*1=2。此时三个城市储备量都为4桶,该状态的平衡度为0。

对于第二组数据,从城市2到城市5运输1桶石油,代价为1*4=4;此时五个城市储备量为(4,4,4,3,3),该状态的非平衡度为1.2,是能达到的所有状态的最小值。

Data Constraint

对于20%的数据,N<=15

对于100%的数据,T<=10,N<=100,0<=si<=10000,1<=X,Y<=N,1<=Z<=10000。

Solution

树形dp。

设sum为石油总和。

对于sum%n=0的情况,每一个点的最终石油量是一样的,为sum/n,那么计算出每一个子树内经过当前边的对答案的贡献即可。

时间O(n)

对于sum%n!=0的情况,两个ave1=sum/n,ave2=sum/n+1。

设f[ i ][ j ]表示以i为根的子树中,有j个值为sum/n+1的点,其余size[ i ]-j个点为sum/n的最优答案。

先枚举儿子。

设g[ ]表示之前处理出的所有儿子的答案。初值g[ 0 ]=g[ 1 ]=0(g[ 1 ]表示当前点x为sum/n+1的点的答案为0)

考虑枚举儿子节点中有k个sum/n+1的点,

那么引入临时中间转移数组F[],F[ j ]=f[ son ][ k ]+g[ j-k ]+cost。

其中cost为abs(  (sum/n+1)的点值*k个+(sum/n)的值*(size [ son ] -k)个  -  sumoil[ son ]子树内现有的石油量  )差值表示需要运进来或运出去的代价* len当前边的长度。

最后再把g赋值成F实现更新即可。

最后答案为f[ 1 ][ sum%n ]。即有sum%n个sum/n+1的点。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define I int
#define ll long long
#define son t[k]
#define F(i,a,b) for(I i=a;i<=b;i++)
#define Fd(i,a,b) for(I i=a;i>=b;i--)
#define mem(a,b) memset(a,b,sizeof a)
#define N 105
using namespace std;
I T,n,a[N],x,y,z,s[N],t[N<<1],nx[N<<1],w[N<<1],ls[N],tot;
ll f[N][N],g[N],F[N],sz[N],sum;
void add(I x,I y,I z){t[++tot]=y,nx[tot]=ls[x],ls[x]=tot,w[tot]=z;}
void dg(I x,I y){
	sz[x]=a[x];
	for(I k=ls[x];k;k=nx[k]) if(son!=y){
		dg(son,x);
		sz[x]+=sz[son];
		f[1][0]+=w[k]*abs(sz[son]-sum/n);
	}
}
void dp(I x,I y){
	s[x]=1,sz[x]=a[x];
	for(I k=ls[x];k;k=nx[k]) if(son!=y){
		dp(son,x);
		sz[x]+=sz[son],s[x]+=s[son];
	}
	mem(g,60);
	g[0]=g[1]=0;
	for(I k=ls[x];k;k=nx[k]) if(son!=y){
		mem(F,60);
		F(i,0,min((ll)s[x],sum%n)){
			F(j,0,min(i,s[son])){
				F[i]=min(F[i],f[son][j]+g[i-j]+abs((sum/n+1)*j+sum/n*(s[son]-j)-sz[son])*w[k]);
			}
		}
		F(i,0,min((ll)s[x],sum%n)) g[i]=F[i];
	}
	F(i,0,min((ll)s[x],sum%n)) f[x][i]=g[i];
}
I main(){
	freopen("plan.in","r",stdin);
	freopen("plan.out","w",stdout);
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		mem(nx,0),mem(ls,0),mem(sz,tot=sum=0);
		F(i,1,n) scanf("%d",&a[i]),sum+=a[i];
		F(i,1,n-1){
			scanf("%d%d%d",&x,&y,&z);
			add(x,y,z),add(y,x,z);
		}
//		if(sum%n==0) f[1][0]=0,dg(1,0);
		dp(1,0);
		printf("%lld\n",f[1][sum%n]);
	}
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值