树形DP

树形DP新手向教程

1.P1352 没有上司的舞会

void dfs(int u){
    for(int i=head[u], v; i; i=next[i]) {
        dfs(v=to[i]); 
        dp[u][0]+=max(dp[v][1], dp[v][0]); 
        dp[u][1]+=dp[v][0];
    } dp[u][1]+=d[u];
}

2.P2014 选课

void dfs(int u){
    dp[u][0]=0;
    for(int i=head[u], v; i; i=next[i]){
        dfs(v=to[i]);
        for(int t=m; t >= 0; t--)
            for(int j=t; j >= 0; j--)
                dp[u][t]=max(dp[u][t], dp[u][t-j]+dp[v][j]);
    }
    if(u != 0) for(int i=m; i > 0; i--) 
        dp[u][i]=dp[u][i-1]+w[u];
}

3.P1273 有线电视网(要记录一下所连叶子节点的个数)

4.P1270 “访问”美术馆(这题输入好神奇)

int dp(int u, int left){
    if(left <= 0) return 0;
    if(f[u][left] != -1) return f[u][left];
    if(!ch[u][0]) return f[u][left]=min(val[u], left/5);
    int ls=ch[u][0], rs=ch[u][1]; 
    if(left > d[ls]) f[u][left]=max(f[u][left], dp(ls, left-d[ls]));
    if(left > d[rs]) f[u][left]=max(f[u][left], dp(rs, left-d[rs]));
    for(int i=d[ls]+1; left-i-d[rs] >= 0; i++) 
        f[u][left]=max(f[u][left], dp(ls, i-d[ls])+dp(rs, left-d[rs]-i));
    return f[u][left];
}

5.P3360 偷天换日(只要在上面那题的基础上加上背包预处理)

6.[HNOI/AHOI2018]道路(这个倒推公式不难得出,但是卡空间巨恶心)

void dfs(int u, int x, int y){
    int p=num[u]=(top ? S[top--] : ++tot); 
    if(!s[u]){
        for(int i=0; i <= x; i++) for(int j=0; j <= y; j++) 
            f[p][i][j]=1LL*c[u]*(a[u]+i)*(b[u]+j); 
        return ;
    }
    dfs(s[u], x+1, y); dfs(t[u], x, y+1);
    int ls=num[s[u]], rs=num[t[u]];
    for(int i=0; i <= x; i++) for(int j=0; j <= y; j++) 
        f[p][i][j]=min(f[ls][i+1][j]+f[rs][i][j], f[ls][i][j]+f[rs][i][j+1]);
    S[++top]=ls, S[++top]=rs;
}

7.[ZJOI2007]时态同步(两次\(dfs\), 一次计算最大值,一次计算答案)

void dfs1(int u){
    for(int i=0, v; i < e[u].size(); i++) if(d[v=e[u][i].first] == 0 && v != s){
        d[v]=d[u]+e[u][i].second; dfs1(v); mx[u]=max(mx[u], max(d[v], mx[v]));
    }
}
ll dfs2(int u, int fa){
    ll ans=0; 
    for(int i=0, v; i < e[u].size(); i++) if((v=e[u][i].first) != fa) 
        ans+=mx[u]-max(mx[v], d[v])+dfs2(v, u);
    return ans;
}

8.[APIO2010]巡逻(第一次\(BFS\)求树的直径, 第二次\(DP\)求树的直径)

//DP求树的直径:
void dfs(int u, int fa){
    for(int i=0, v; i < e[u].size(); i++) if((v=e[u][i].first) != fa){
        dfs(v, u); 
            l1=max(l1, d[u]+d[v]+e[u][i].second); 
            d[u]=max(d[u], d[v]+e[u][i].second);
    }
}

9.hihoCoder#1763 : 道路摧毁
\(dp[i][j]\)表示把点\(i\)只与\(j\)集合相连的最小代价

void dfs(int u, int fa){
    if(belong[u] == 1) dp[u][2]=inf; if(belong[u] == 2) dp[u][1]=inf;
    for(int i=head[u], v; i; i=nxt[i]) if((v=to[i]) != fa){
        dfs(v, u);
        if(belong[u] != 2) dp[u][1]+=min(dp[v][1], dp[v][2]+w[i]);
        if(belong[u] != 1) dp[u][2]+=min(dp[v][2], dp[v][1]+w[i]);
    }
}

10.[ZJOI2008]骑士(去掉环上的一条边,从两端点开始\(DP\), 最后再把两端点的合并)

void find(int u, int fa){
    vis[u]=1;
    for(int i=head[u], v; i; i=nxt[i]) 
        if((v=to[i]) != fa){
        if(vis[v]){e=i; x1=u, x2=v; continue;}
        find(v, u);
    }
}
void dfs(int u, int fa){
    dp[u][0]=0; dp[u][1]=val[u];
    for(int i=head[u], v; i; i=nxt[i]) 
        if(i != e && (i^1) != e && (v=to[i]) != fa){
        dfs(v, u); 
            dp[u][0]+=max(dp[v][0], dp[v][1]); 
            dp[u][1]+=dp[v][0];
    }
}
// in main
for(int i=1; i <= n; i++) if(!vis[i]){
    find(i, 0); 
        dfs(x1, 0); ll delta=dp[x1][0]; 
        dfs(x2, 0); ans+=max(delta, dp[x2][0]);
}

11.[IOI2008]Island(判环磨死我了,展现了\(toposort\)\(dfs\)的优势\(QAQ\))

void dp(int color, int s){
        int m=0, i, u=s, len=0;
        do{
            a[++m]=f[u]; dep[u]=1;
            for(i=head[u]; i; i=nxt[i]) if(dep[to[i]] > 1) 
                {u=to[i]; b[m+1]=b[m]+w[i]; break;}
        }while(i);
        if(m == 2){
            for(int i=head[u]; i; i=nxt[i]) if(to[i] == s) len=max(len, w[i]);
            d[color]=max(d[color], f[u]+f[s]+len); return ;
        }
        for(i=head[u]; i; i=nxt[i]) if(to[i] == s) {b[m+1]=b[m]+w[i]; break;}
        for(i=1; i < m; i++) a[m+i]=a[i], b[m+i]=b[m+1]+b[i];
        q[L=R=1]=1;
        for(i=2; i < m*2; i++){
            while(L <= R && i-q[L] >= m) L++;
            d[color]=max(d[color], a[i]+a[q[L]]+b[i]-b[q[L]]);
            while(L <= R && a[q[R]]-b[q[R]] <= a[i]-b[i]) R--;
            q[++R]=i;
        }
}

12.[HNOI2014]米特运输

虚树入门
1.[SDOI2011]消耗战(树链剖分分配\(dfn\)与求\(Lca\), 再在虚树上\(DP\),重点在虚树的构建上)

void ins(int x){
    if(top == 1) {sta[++top]=x; return ;}
    int lca=LCA(sta[top], x); 
    if(lca == sta[top]) return ;
    while(top > 1 && dfn[sta[top-1]] >= dfn[lca]) pb(sta[top-1], sta[top]), top--;
    if(lca != sta[top]) pb(lca, sta[top]), sta[top]=lca; sta[++top]=x;
}
ll DP(int u){
    if(!G[u].size()) return mn[u]; ll sum=0;
    for(int i=0; i < G[u].size(); i++) sum+=DP(G[u][i]);
    G[u].clear(); return min(mn[u], sum);
}
// in main
   while(m--){
    int k=read(); for(int i=1; i <= k; i++) pk[i]=read(); sort(pk+1, pk+1+k, cmp);
    sta[top=1]=1; for(int i=1; i <= k; i++) ins(pk[i]);
    while(top) pb(sta[top-1], sta[top]), top--;
    printf("%lld\n", DP(1));
}

2.[HNOI2014]世界树

3.[SDOI2015]寻宝游戏

猜你喜欢

转载自www.cnblogs.com/zerolt/p/9282386.html