Codeforces contest 856 D(树形DP,树剖)

超链接:http://codeforces.com/contest/856/problem/D

题意就是给你一棵树,和一些树上的链,每个链有一个权值,要求求一个不相交链的集合,使得权值最大.

两条链相交定义为这两条链有公共点.

那么这题非常的难,于是我不会做......

咳咳咳.......

我于是就学了一发正解.

f[i]表示在以i为根的子树中的答案(仅能包含在完全在子树内的链)

那么把链集中压到两端点lca的vector里去.

设sum[i]=sigma(f[son[i]])

很好理解

f[i]=max(sum[i],对每条在i的vector里的链的可能答案);

那么怎么计算后者呢?

如图:

无标题

现在要更新f[1]

红色的链为考虑的链,那么它的可能答案就是f[5]+f[6]+f[7]+f[11]+1

变形一下就是sum[4]+sum[3]-f[4]+sum[2]-f[3]+sum[1]-f[2]+sum[10]+sum[1]-f[10]-sum[1]+1

sum[1]加了两遍,所以要减去一次.

那么你就可以根据这个写出DP了

复杂度是O(n+m)(Tanjan LCA)+O(n*链的期望长度)(树形DP)

但是这样只能过随机数据,不能过hack数据.

于是考虑树剖优化.

维护contribution[i]表示从这条重链底端到它的sigma(sum[i]-f[son[i]])(此处son[i]表示重儿子)

每次跳的时候直接到链顶.

再乱计算一波就可以了.

贴上我的乱计算代码:

int calc(int i,int x){ int y=u[i],z=v[i],res=sum[y]+sum[z]; while (top[y]!=top[x]) res+=contribution[top[y]]-contribution[y]+sum[fa[top[y]]]-dp[top[y]],y=fa[top[y]]; res+=contribution[x]-contribution[y]; while (top[z]!=top[x]) res+=contribution[top[z]]-contribution[z]+sum[fa[top[z]]]-dp[top[z]],z=fa[top[z]]; res+=contribution[x]-contribution[z]; return res-sum[x]+money[i]; }i是链的编号,top是链顶,money是权值.然后这题就可以过了.悲伤的是我竟然写了一个tanjan lca 其实可以沿用树剖.

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=200010;
int n,m,x,y,k,ancestor[N],fa[N],fi[N],u[N],v[N],sum[N],dp[N],ne[N<<1],b[N<<1],money[N],sz[N],top[N],son[N],contribution[N];
vector<pair<int,int> >g[N];
vector<int>w[N];
inline int read() {
    char ch=getchar(); int x=0;
    while(ch<'0'||ch>'9') ch=getchar();
    while('0'<=ch&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}
inline void add(int x,int y){b[++k]=y; ne[k]=fi[x]; fi[x]=k;}
inline int ask(int x){return x==ancestor[x]?x:ancestor[x]=ask(ancestor[x]);}
inline int ask2(int x){return x==top[x]?x:top[x]=ask2(top[x]);}
inline void dfs(int x){
    sz[x]=1;
    for (int j=fi[x]; j; j=ne[j]){
        fa[b[j]]=x;
        dfs(b[j]);
        ancestor[b[j]]=x;
        sz[x]+=sz[b[j]];
        if (sz[b[j]]>sz[son[x]]) son[x]=b[j];
    }
    top[son[x]]=x;
    for (vector<pair<int,int> >::iterator it=g[x].begin(); it!=g[x].end(); it++)
        if (it->second==1||fa[it->second]) w[ask(it->second)].push_back(it->first);
}
int calc(int i,int x){
    int y=u[i],z=v[i],res=sum[y]+sum[z];
    while (top[y]!=top[x]) res+=contribution[top[y]]-contribution[y]+sum[fa[top[y]]]-dp[top[y]],y=fa[top[y]];
    res+=contribution[x]-contribution[y];
    while (top[z]!=top[x]) res+=contribution[top[z]]-contribution[z]+sum[fa[top[z]]]-dp[top[z]],z=fa[top[z]];
    res+=contribution[x]-contribution[z];
    return res-sum[x]+money[i];
}
void getans(int x){
    for (int j=fi[x]; j; j=ne[j]){
        getans(b[j]);
        sum[x]+=dp[b[j]];
    }
    dp[x]=sum[x];
    contribution[x]=contribution[son[x]]+sum[x]-dp[son[x]];
    for (vector<int>::iterator it=w[x].begin(); it!=w[x].end(); it++) dp[x]=max(dp[x],calc(*it,x));
}
int main(){
    n=read(); m=read();
    for (int i=2; i<=n; i++) add(read(),i);
    for (int i=1; i<=m; i++){
        u[i]=read(); v[i]=read(); money[i]=read();
        g[u[i]].push_back(make_pair(i,v[i]));
        g[v[i]].push_back(make_pair(i,u[i]));
    }
    for (int i=1; i<=n; i++) ancestor[i]=top[i]=i;
    dfs(1);
    for (int i=1; i<=n; i++) ask2(i);
    for (int i=1; i<=n; i++) w[i].erase(unique(w[i].begin(),w[i].end()),w[i].end());
    getans(1);
    printf("%d",dp[1]);
}

 

不知道是不是我写炸了,当两个端点有父子关系或相等时,tajan lca 会把一个lca压入vector两次.于是有奇怪的话

for (int i=1; i<=n; i++) w[i].erase(unique(w[i].begin(),w[i].end()),w[i].end());

纯属作死.

当然不加这句话也没事,只会让程序变慢而已.

 调代码
posted @ 2017-09-30 20:02  Yuhuger  阅读(174)  评论(0编辑  收藏  举报