CEOI 2017 Chase

题意

给出一棵树,求一条路径,选择路上的V个点,使得被选择的点的相邻且不在路径上的点的权值和最大。

思路

定义:

c[i][j] c [ i ] [ j ] 为从 i i 点的子树中走到i,选择j个点的权值和。

b[i][j]为从 i i 点开始,向子树中走,选择j个点的权值和。

g[i] i i 的相邻节点的权值和。

fa[i] i i 的父节点。

F[i] i i 的权值。

初始值:

c[x][0]=0,c[x][i]=g[x]

b[x][0]=0,b[x][i]=g[x]F[fa[x]] b [ x ] [ 0 ] = 0 , b [ x ] [ i ] = g [ x ] − F [ f a [ x ] ]

转移方程:

c[x][i]=max(c[y][i],c[y][i1]+g[x]F[y]) c [ x ] [ i ] = m a x ( c [ y ] [ i ] , c [ y ] [ i − 1 ] + g [ x ] − F [ y ] )

b[x][i]=max(b[y][i],b[y][i1]+g[x]F[fa[x]]) b [ x ] [ i ] = m a x ( b [ y ] [ i ] , b [ y ] [ i − 1 ] + g [ x ] − F [ f a [ x ] ] )

代码

#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
const int maxn=1e5+5;
int N,V,x,y,tot,top,F[maxn],sta[maxn];
int son[maxn<<1],nxt[maxn<<1],lnk[maxn];
long long g[maxn],c[maxn][105],b[maxn][105],ans;
inline int read() {
    int ret=0,f=1,ch=getchar();
    for (; !isdigit(ch); ch=getchar()) if (ch=='-') f=-f;
    for (; isdigit(ch); ch=getchar()) ret=ret*10+ch-48;
    return ret*f;
}
inline void add_edge(int x,int y) {
    son[++tot]=y,nxt[tot]=lnk[x],lnk[x]=tot;
    son[++tot]=x,nxt[tot]=lnk[y],lnk[y]=tot;
}
inline void DP(int x,int y,int f) {
//  ans一定要在前面赋值,这样可以避免从y走到x又走回y
    for (int i=1; i<=V; ++i) ans=max(ans,c[x][i]+b[y][V-i]);
    for (int i=1; i<=V; ++i)
        c[x][i]=max(c[x][i],max(c[y][i],c[y][i-1]+g[x]-F[y])),
        b[x][i]=max(b[x][i],max(b[y][i],b[y][i-1]+g[x]-F[f]));
}
void dfs(int x,int pre) {
    for (int i=1; i<=V; ++i) c[x][i]=g[x],b[x][i]=g[x]-F[pre];
    for (int k=lnk[x]; k; k=nxt[k]) if (son[k]^pre) dfs(son[k],x),DP(x,son[k],pre);
//  对于节点x,y和z是它的子节点并且,y先于z遍历。
//  上面的方法,无法计算z->x->y所以要倒着做一遍
    for (int i=1; i<=V; ++i) c[x][i]=g[x],b[x][i]=g[x]-F[pre];
    top=0;for (int k=lnk[x]; k; k=nxt[k]) if (son[k]^pre) sta[++top]=son[k];
    for (int i=top; i; --i) DP(x,sta[i],pre);
    ans=max(ans,max(c[x][V],b[x][V]));
}
int main() {
    N=read(),V=read();
    for (int i=1; i<=N; ++i) F[i]=read();
    for (int i=1; i<N; ++i) add_edge(x=read(),y=read()),g[x]+=F[y],g[y]+=F[x];
    return dfs(1,0),printf("%lld",ans),0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值