[八省联考2018]林克卡特树lct——WQS二分
一看这种题就不是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二分