OI题解 - 道路(HNOI/AHOI) | Lucky_Glass's Blog
0%

OI题解 - 道路(HNOI/AHOI)

儿童节快乐~ 尽管从某种程度上我早就不是那个能过六一节的儿童了


# 题面

> 洛谷 P4438


# 解析

题目中非常重要的提示是“树深不超过40”(可惜我以为这是限制答案不超过long long……不过讲道理限制树深确实非常特殊,应该有特别意义)

因为代价的计算式中出现了 $x,y$ 的乘积项,并不方便处理,这就意味着 DP 状态中一定要储存 $x,y$ 的相关信息。对于一般的树形DP,我们往往会储存一个子树的信息,但是这道题我们可以储存当前点到根的路径上的信息:

定义 f[u][x][y] 表示 $u$ 到 $1$ 的路径上有 $x$ 条未翻新的公路、$y$ 条未翻新的铁路对应的最小子树代价和

不难得到转移式:

$v_x$ 指 $u$ 通过公路连接的点,$v_y$ 指 $u$ 通过铁路连接的点。那么 $\min$ 中前者表示修复公路、后者表示修复铁路。

如果 $v$ 是乡村,则 $f[v][x][y]=(a_{v}+x)(b_v+y)c_v$。

然后从下往上DP即可,又因为出题人非常好心地让树的节点标号和树深相联系,甚至都不需要写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
38
39
40
41
42
43
44
45
46
47
48
/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long ll;
const int N=2e4+3;

int n;
int lnk[N][2],val[N][3],fa[N<<1],cntx[N<<1],cnty[N<<1];
ll f[N][41][41];

#define F(u,x,y) ((u)>0? f[u][x][y]:1ll*val[-u][2]*(val[-u][0]+x)*(val[-u][1]+y))
inline int Ri(){
register int a=0,b=1,c=getchar();
while(c<'0' || '9'<c) b=(c=='-'? -1:b),c=getchar();
while('0'<=c && c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
return a*b;
}
int main(){
n=Ri();
for(int i=1;i<n;i++)
for(int j=0;j<2;j++){
lnk[i][j]=Ri();
if(lnk[i][j]>0) fa[lnk[i][j]]=i;
}
for(int i=1;i<=n;i++)
for(int j=0;j<3;j++)
val[i][j]=Ri();
memset(f,0x3f,sizeof f);
for(int u=2;u<n;u++){
cntx[u]=cntx[fa[u]]+(lnk[fa[u]][0]==u);
cnty[u]=cnty[fa[u]]+(lnk[fa[u]][1]==u);
}
for(int u=n-1;u>=1;u--){
int vx=lnk[u][0],vy=lnk[u][1];
for(int x=0;x<=cntx[u];x++)
for(int y=0;y<=cnty[u];y++){
//x
f[u][x][y]=min(f[u][x][y],F(vx,x,y)+F(vy,x,y+1));
//y
f[u][x][y]=min(f[u][x][y],F(vx,x+1,y)+F(vy,x,y));
}
}
printf("%lld\n",f[1][0][0]);
return 0;
}

THE END

Thanks for reading!