题目大意:给你一棵有n个点的树,根节点编号0,现在Alice 和 Bob要去旅行,Alice 想要边的和尽可能小,Bob想要尽量大,但是两个人都必须要使这个和在区间 [ l , r ],每人轮流选一条边,Bob 开始选,问你最大的和。
思路:树形DP,设d[ u ][ 0 ] 表示以u为根节点的子树,先有 Alice 选的最大和,d[ u ][ 1 ] 表示 Bob 先选的最大和,由于树中从根节点到任意一点的路径是唯一的,d[ u ][ 0 ] 获得的值其实是d[ u ][ 0 or 1 ] + dis(root , u),转移时要保证这个值要在 [ l , r ] 区间内,状态转移方程为 d[ u ][ 0 ] = min(d[ v ][ 1 ] + val( u, v )),d[ u ][ 1 ] = max(d[ v ][ 0 ] + val( u , v) ) ,同时满足前面说的区间的条件。
自己想的时候,状态表示、状态方程是挺简单的,可就是那个区间不好处理,我知道如果 d[ u ] > l 了,那么它就肯定不能去转移了,因为上面还有权值,那么 r 这里也不好处理,还有 就算 满足 d[ u ] <= l 了,也不能确定转移,因为上面的加起来可能会爆 r ,于是就没有办法了。。。 后来一看别人博客,他刚提到记录根节点到这个节点的距离和,这才恍然大悟,路径是唯一的呀,马上敲完,1A了。。。 唉,总是差一点,差一点。。。 = =
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF = 0x0fffffff;
const int MAXN = 500055;
int n,l,r;
struct Edge
{
int t,next,val;
}edge[MAXN];
int tot,head[MAXN];
void add_edge(int s,int t,int val)
{
edge[tot].t = t;
edge[tot].val = val;
edge[tot].next = head[s];
head[s] = tot++;
}
int dis;
int check(int x)
{
if( x <= r && x >= l) return 1;
else return 0;
}
int d[MAXN][2];
void dfs(int u)
{
d[u][0] = d[u][1] = 0;
for(int e = head[u];e != -1;e = edge[e].next)
{
int v = edge[e].t;
int val = edge[e].val;
dis += val;
dfs(v);
dis -= val;
int tmp = dis + val + d[v][1];
//printf("tmp1 = %d\n",tmp);
if(check(tmp))
d[u][0] = min(val + d[v][1],(d[u][0] == 0 ? INF:d[u][0]));
tmp = dis + val + d[v][0];
//printf("tmp2 = %d\n",tmp);
if(check(tmp))
d[u][1] = max(val + d[v][0],d[u][1]);
}
//printf("u = %d,d0 = %d,d1 = %d\n",u,d[u][0],d[u][1]);
}
int main()
{
while(~scanf("%d%d%d",&n,&l,&r))
{
tot=0;
memset(head,-1,sizeof(head));
for(int i = 1;i<n;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add_edge(a,b,c);
}
dis = 0;
dfs(0);
if(d[0][1] == 0) puts("Oh, my god!");
else printf("%d\n",d[0][1]);
}
return 0;
}