[八省联考2018]林克卡特树lct——WQS二分

 [八省联考2018]林克卡特树lct

一看这种题就不是lct。。。

除了直径好拿分,别的都难做。

所以必须转化

突破口在于:连“0”边

对于k=0,我们求直径

k=1,对于(p,q)一定是从p出发,走一段原树,走0(或不走),再走一段原树,所以要最大化原树的值的和。

选择最大两条 点不相交的链(注意:可以选择一个点,这时候链长为0)。然后一定可以首尾连起来得到答案

k更大的时候,选择最大的k+1条两两不相交的路径,然后一定存在方案使之连接起来,一定是最优解。(因为如果实际上最优解不用走k条0边,一定会把这些0边随便连一连废掉,对应选择一个点作为链)

 

所以,求最大的k+1条两两点不相交的路径。

点不相交,每次贪心取直径然后取反其实不好做。而且显然扩展性太差

树形DP

f[x][0/1/2][k]表示x为根的子树,从下面连接到x的度数是0/1/2,用k条链的最优解。特别地,从x开始往上的链归入f[x][1][*]。

转移时候枚举和当前儿子怎么连就好了。

 

看上去已经不能优化了。

然鹅

可以发现,如果f[x]函数表示选择x个链的最大总和

这是一个上凸函数!

就可以WQS二分了

 

具体地,每个链的额外花费mid的代价

然后求全局的最高点。没了k的限制就好做了

f[x][0/1/2]

(PS:

1.这个题如果连接的新边不是0应该也可以,但是必须是正数,负数的话转化就不对了,不一定走K次新边最优

2.如果k是一个区间也许也可以?

代码:

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=3e5+5;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,k;
int b[N][3];
struct node{
    int nxt,to;
    ll val;
}e[2*N];
int hd[N],cnt;
void add(int x,int y,ll z){
    e[++cnt].nxt=hd[x];
    e[cnt].val=z;
    e[cnt].to=y;
    hd[x]=cnt;
}
struct dp{
    ll v;
    int c;
    dp(){}
    dp(ll a,int b){
        v=a;c=b;
    }
    dp operator +(const dp &b){
        return dp(v+b.v,c+b.c);
    }
    bool friend operator <(dp a,dp b){
        return (a.v<b.v||(a.v==b.v&&a.c<b.c));
    }
    void clear(){
        v=-inf;c=-0x3f3f3f3f;
    }
}f[N][3];
ll sum;
void dfs(int x,int fa,ll mid){
    f[x][2].clear();
    f[x][1].clear();
    f[x][0].v=0,f[x][0].c=0;
    for(reg i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        dfs(y,x,mid);
        f[x][2]=max(f[x][2]+f[y][0],f[x][1]+f[y][1]+dp(e[i].val+mid,-1));
        f[x][1]=max(f[x][1]+f[y][0],f[x][0]+f[y][1]+dp(e[i].val,0));
        f[x][0]=f[x][0]+f[y][0];
    }
    f[x][1]=max(f[x][1],f[x][0]+dp(-mid,1));
    f[x][0]=max(f[x][0],max(f[x][1],f[x][2]));
}
int check(ll mid){
    memset(hd,0,sizeof hd);cnt=0;
    sum=0;
    for(reg i=1;i<n;++i){
        add(b[i][0],b[i][1],b[i][2]);
        add(b[i][1],b[i][0],b[i][2]);
    }
    dfs(1,0,mid);
//    for(reg i=1;i<=n;++i){
//        cout<<" i "<<i<<" : "<<f[i][0].v<<" "<<f[i][0].c<<endl;
//    } 
    sum=f[1][0].v;
//    cout<<" mid "<<mid<<" :: "<<sum<<" "<<f[1][0].c<<endl;
    return f[1][0].c;
}
int main(){
    rd(n);rd(k);
    ++k;
    ll l=0,r=0;
    for(reg i=1;i<n;++i){
        rd(b[i][0]);rd(b[i][1]);rd(b[i][2]);
        r+=abs(b[i][2]);
    }l=-r;
    ll ans=-233;
    while(l<=r){
        ll mid=(l+r)/2;
        if(check(mid)>=k) ans=mid,l=mid+1;
        else r=mid-1;
    }
    int haha=check(ans);
    printf("%lld\n",sum+(ll)k*ans);
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2019/2/20 15:23:18
*/

 总结:

1.转化为k+1个链

2.树形dp(经典问题)

3.凸函数,WQS二分

转载于:https://www.cnblogs.com/Miracevin/p/10407833.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值