P4383 [八省联考2018]林克卡特树
分析
对于题目要求的删除 (k) 条边,再连接 (k) 条边,分析一下,我们发现,实际上再连接的 (k) 条边,由于它们的权值是 (0) ,连接与否效果都是一样,那么,我们就只考虑如何删除那 (k) 条边即可,于是,这个问题我们就可以转化为将一棵树上选出 (k+1) 条链,然后拼起来的总权值和最大
对于此类树上 dp 问题,可以设 (dp[i][j]) 表示以 (i) 为根的子树,在该子树内选择了 (j) 条边的最大值
但是这样设计状态的话可以发现,如果两个点之间进行了连边,我们是不容易知道此时连边的具体情况的,因为这个点可能在一条链的端点(起点和终点),或者是一条链上的中间的一个节点,或者是不在链上,因此难以进行转移
那么,对于原树中的一个点,进行连边之后,每个点的度数应该为 (0/1/2) ,因此,我们可以再添加一维,变成 (dp[i][j][0/1/2]) 表示以 (i) 为根的子树内选择了 (j) 条边,且 (i) 的度数为 (0/1/2) 时的最大值,其中度数为 (0) 表示该点不在所选链中, (1) 表示这个点拖着一条未完成的链,为 (2) 表示这个点被连接两个子树的链相连,我们在这里把一个点也看作是一个为完成的链
如果当前 (u) 节点的度数是 (0) ,那么它显然并不在最后形成的答案路径里,那么直接进行更新即可
如果当前节点 (u) 的度数是 (1) ,那么此时,它可以是拖着一条没有完成的链的状态,那么此时的答案就是它自己先前的值,再加上自己儿子不在链上时,其子树内的最大权值和;另一种情况,就是此时它度数是 (1) ,就是它与它的儿子相连,在这种情况下,它的儿子的应该是一个挂着一条未完成的链时的情况,由于在连边之前,节点 (u) 是 (0) 度的,就需要从 (dp[u][0][k]) 转移过来
如果当前节点的度数为 (2) ,同上,对于当前节点 (u) ,可以直接继承子节点的最大值,或者,这个点是先前挂着一条链,然后与挂着一条链的子节点相连,得到一条更长的链,使得该点被两个链相连
该方法显然是 (O(nk^2)) 的复杂度
优化
经过打表 ,可以发现这个题中设以 (k) 为横坐标,对应的最大值为纵坐标时所构成的平面图形为一个凸壳,此时我们可以考虑进行 WQS 优化
那么按照套路,将树形 dp 中的第三维的条件限制去掉,转变为增加惩罚值,直接二分一个值,带入 check 即可
注意在此时,我们消除了 dp 的第三维的限制,那么就需要特殊处理一下转移的顺序,即旋转 (dp[i][2]) ,再转移 (dp[i][1]) ,最后转移 (dp[i][0])
在 dp 最后,要将子树的信息进行合并,准备向上传递,那么就直接都合并到 (dp[i][0]) 上即可
在初始化的时候,一开始就把初值都变为 (0) ,但是对于一个点,我们认为它实际上是一条并没有完成的链,所以就要注意在初始化时将 (dp[i][1]) 进行的值设为 (-val) ,即为惩罚值,并且将此时的选择条数记为 (1)
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<queue>
#include<climits>
#define ll long long
#define ld long double
using namespace std;
inline ll read()
{
ll x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
const ll inf=1e16+10;
const ll maxn=3e5+10;
ll tot,ans,n,k,l,r,mid;
ll head[maxn<<1];
struct edge
{
ll u,v,nxt,w;
} s[maxn<<1];
struct node
{
ll v,d;
node operator + (const node &a) const
{
node c;
c.v=v+a.v;
c.d=d+a.d;
return c;
}
bool operator < (const node &a) const
{
if(a.v==v)
{
return d<a.d;
}
return v<a.v;
}
} f[maxn][3];
inline ll _abs(ll x)
{
return x<0 ? -x : x;
}
inline void add(ll u,ll v,ll w)
{
s[++tot].v=v;
s[tot].w=w;
s[tot].nxt=head[u];
head[u]=tot;
}
inline void dp(ll x,ll fa,ll val)
{
for(int i=head[x];i;i=s[i].nxt)
{
ll v=s[i].v;
ll w=s[i].w;
if(v==fa) continue;
dp(v,x,val);
f[x][2]=max(f[x][2]+f[v][0],f[x][1]+f[v][1]+node{w,0}+node{-val,1ll});
f[x][1]=max(f[v][1]+f[x][0]+node{w,0ll},f[x][1]+f[v][0]);
f[x][0]=f[v][0]+f[x][0];
}
f[x][0]=max(f[x][0],max(f[x][1]+node{-val,1ll},f[x][2]));
}
inline node judge(ll x)
{
for(int i=1;i<=n;i++)
{
f[i][0].v=f[i][1].v=0;
f[i][0].d=f[i][1].d=0;
f[i][2].v=-x;
f[i][2].d=1;
}
dp(1,0,x);
return f[1][0];
}
int main(void)
{
n=read(),k=read();
k++;
for(int i=1;i<=n-1;i++)
{
ll x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
r+=_abs(z);
l-=_abs(z);
}
while(l<r)
{
mid=(l+r)>>1;
node x=judge(mid);
if(x.d>k)
{
ans=x.v+mid*k;
l=mid+1;
}
else r=mid;
}
printf("%lld
",ans);
return 0;
}