JSOI2018 DAY 2 T2 林克卡特树

题意

自己去找吧
转化之后大概就是给你一个有n个点的值,有边权,然后要你选出k+1条不相交的链,问选择的所有边的和的最大值

题解

先考虑一下60分,k是<=100的,那么我们不妨设f[i][j][0/1/2]表示当前我已经做完了i的子树,点i的度数是0/1/2,对于这一颗子树我们最多能选择的边权和是多少
现在题目的关键是要恰好选择k+1条不相交的链,而选多了选少了答案都可能会变大,这逼迫着我们无法避免的要去设那个第二维,现在我们想要不去设那个第二维

之前做过一道题目是选择1到n一条路径,使得其前k大的边的和最小,在那一题里面,我们强制把每一条边先min(0,x-l),然后最后加上k*l,这样做的好处就是不需要具体的设出选了多少条边权大于等于l的边,那么这一题之中我们其实也可以类似的做法

我们设ans(x)表示当k=x时的答案,我们对其差分,发现差分后的数组是单调递减的,不妨对其做出一个图像,大概是一个山包一样的东西,我们其实就是要获得ans(k),但是这个点不一定是最高的,现在如果我们能使得它变成最高的那个点,那么就不用考虑选了多少个了,只用找最值就可以了

那么我们不妨设 ans’(x)=ans(x)+x*p 只要我们选择的这个p可以使得ans’(k)是ans’()中最高的那一个点,那么我们就可以直接dp了

现在的问题就变成了如何求p,前面说过差分后数组单调,所以说随着p的增加,函数ans’(x)的图像的最值对应的横坐标是不断往右移的(也就是说是单调的),那么其实我们可以二分这个增加值p

在具体的实现之中,我们可以把k+1条链当成k次合并

贴代码

#include
#include
#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define ll long long

using namespace std;

const int maxn=3e5+5;

int fi[maxn],ne[maxn*2],dui[maxn*2],dui1[maxn*2],qc[maxn];
ll f[maxn][3],t1[3],l,r,mid;
int g[maxn][3],t2[3];
int i,j,k,x,y,z,n,now;

void add(int x,int y,int z){
    if (fi[x]==0) fi[x]=++now; else ne[qc[x]]=++now;
    dui[now]=y; dui1[now]=z; qc[x]=now;
}
void merge(int x,int y,int z){
    int i,j,s2; ll s1;
    fo(i,0,2){
        t1[i]=f[y][2]+f[x][i]+mid;
        t2[i]=g[y][2]+g[x][i]+1;
    }
    fo(i,0,2)
        fo(j,0,2-i){
            s1=f[x][i]+f[y][j];
            if (j==1) s1=s1+z;
            s2=g[x][i]+g[y][j];
            if (s1>t1[i+j]){
                t1[i+j]=s1; t2[i+j]=s2;
            } else if (s1==t1[i+j] && s20,1){
        if (t1[i]>t1[i+1]){
            t1[i+1]=t1[i]; t2[i+1]=t2[i];
        } else if (t1[i]==t1[i+1] && t2[i]0,2){
        f[x][i]=t1[i]; g[x][i]=t2[i];
    }
}
void dfs(int x,int y){
    for(int i=fi[x];i;i=ne[i]) if (dui[i]!=y){
        dfs(dui[i],x);
        merge(x,dui[i],dui1[i]);
    }
}
int main(){
    freopen("lct.in","r",stdin);
    freopen("lct.out","w",stdout);
    scanf("%d%d",&n,&k);
    fo(i,1,n-1){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z); add(y,x,z);
    }
    l=-3e11; r=1e6;
    while (l1)/2;
        memset(f,0,sizeof(f));
        memset(g,0,sizeof(g));
        dfs(1,0);
        if (l==r || g[1][2]==k) break;
        if (g[1][2]else r=mid-1;
    }
    printf("%lld\n",f[1][2]-mid*k);
    return 0;
}

你可能感兴趣的:(DP)