题目描述
几乎整个Byteland王国都被森林和河流所覆盖。小点的河汇聚到一起,形成了稍大点的河。就这样,所有的河水都汇聚并流进了一条大河,最后这条大河流进了大海。这条大河的入海口处有一个村庄——名叫Bytetown。
在Byteland国,有n个伐木的村庄,这些村庄都座落在河边。目前在Bytetown,有一个巨大的伐木场,它处理着全国砍下的所有木料。木料被砍下后,顺着河流而被运到Bytetown的伐木场。Byteland的国王决定,为了减少运输木料的费用,再额外地建造k个伐木场。这k个伐木场将被建在其他村庄里。这些伐木场建造后,木料就不用都被送到Bytetown了,它们可以在运输过程中第一个碰到的新伐木场被处理。显然,如果伐木场座落的那个村子就不用再付运送木料的费用了。它们可以直接被本村的伐木场处理。
注:所有的河流都不会分叉,形成一棵树,根结点是Bytetown。
国王的大臣计算出了每个村子每年要产多少木料,你的任务是决定在哪些村子建设伐木场能获得最小的运费。其中运费的计算方法为:每一吨木料每千米1分钱。
输入输出格式
输入格式:
第一行包括两个数 n(2≤n≤100),k(1≤k≤50,且 k≤n)。n为村庄数,k为要建的伐木场的数目。除了Bytetown外,每个村子依次被命名为1,2,3……n,Bytetown被命名为0。
接下来n行,每行3个整数:
wi——每年i村子产的木料的块数(0≤wi≤10000)
vi——离i村子下游最近的村子(即i村子的父结点)(0≤vi≤n)
di——vi到i的距离(千米)。(1≤di≤10000)
保证每年所有的木料流到bytetown的运费不超过2,000,000,000分
50%的数据中n不超过20。
输出格式:
输出最小花费,单位为分。
输入输出样例
4 2 1 0 1 1 1 10 10 2 5 1 2 3
4
ps;题面沾了落谷的;
一个不错的树形dp题
由题意来看 这是一颗树
我们可以将节点和可支配伐木场数作为状态,再对每一节点的子节点分配伐木场数即可;
但这样显然是不行的,先不谈时间的问题,单是“对每一节点分配伐木场数”这一要求就难以利用代码实现(焫鷄认为)
所以我们秉承着复杂问题简单化的思想 把他化简为一个左儿子右兄弟的二叉树,这样我们对于每一个节点,只需分配两个子节点即可;
这样提交上去,只能拿到30分,时间大大超时。
那我们就应该考虑一下该如何优化了。
首先对于dp问题我们必须是要用到记忆化搜索的(或是状态转移方程),但是如果单单的记录节点和伐木数这两种状态也是很复杂的,因为对于一个点来说,他拥有的伐木数确定并不意味着这个点是唯一的,因为之前的伐木数分配我们并不清楚,所以我们决定再加上一个状态,即之前的伐木数分配。但其实我们并不需要记录之前所有的分配,只需记录与该点有关的即可,也就是离该点最近的那个伐木场的位置,有了这三个状态,程序的效率就会高上许多,但这个脏题是真的脏,这还是不掉,只能拿到50分。
我们继续思考,为什么会超时,经过思考之后发现,由于程序分配伐木场数是枚举的,所以可能会出现以下情况: 该点之下的点只剩下n个,而可用的伐木场数k>n;
在这种情况下,我们不必继续搜索,直接返回0即可,加上这个优化,就终于能拿到这不容易的AC了
/*
jzoj 1306
2017 3 27 ;
by century
*/
#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int dis[100000];
int w[100000];
int v[100000];
int a[1000][1000];
int top[10000];
int child[1000000];
int brother[1000000];
int n,k;
int f[200][200][200];
int ppp[10000];
int diss(int x,int dix,int father)
{
dis[x]=dix;
for(int i=1;i<=n;i++)
{
if(a[x][i]!=-1)
{
if(i!=father)
ppp[x]+=diss(i,dix+a[x][i],x);
}
}
return ppp[x]+1;
}
int vvv(int x)
{
if(x==-1)return 0;
ppp[x]=vvv(child[x])+vvv(brother[x]);
return ppp[x]+1;
}
int dfs(int x,int str,int num,int k)
{
if(x==-1)return 0;
if(f[x][num][str])return f[x][num][str];
if(num>ppp[x])return 0;
int ans=-1;
//x不选
for(int i=0;i<=num;i++)
{
if(ans==-1||ans>dfs(child[x],str,i,k+1)+dfs(brother[x],str,num-i,k+1))
ans=dfs(child[x],str,i,k+1)+dfs(brother[x],str,num-i,k+1);
}
if(ans==-1)
ans=w[x]*(dis[x]-dis[str]);
else ans+=w[x]*(dis[x]-dis[str]);
if(num>0&&x!=0)//x选
{
int sum=-1;
int kk=num-1;
for(int i=0;i<=kk;i++)
{
if(sum==-1||dfs(child[x],x,i,k+1)+dfs(brother[x],str,kk-i,k+1)<sum)
sum=dfs(child[x],x,i,k+1)+dfs(brother[x],str,kk-i,k+1);
}
if(sum==-1)sum=0;
return f[x][num][str]=min(ans,sum);
}
return f[x][num][str]=ans;
}
int main()
{
memset(a,-1,sizeof(a));
memset(child,-1,sizeof(child));
memset(brother,-1,sizeof(brother));
cin>>n>>k;
for(int i=1;i<=n;i++)
{
int vv;
cin>>w[i]>>vv>>v[i];
a[i][vv]=v[i];
a[vv][i]=v[i];
brother[i]=child[vv];
child[vv]=i;
}
diss(0,0,-1);
vvv(0);
cout<<dfs(0,0,k,0)<<endl;
return 0;
}