bzoj 1040: [ZJOI2008]骑士(基环树DP,基环树上最大点权独立集)

本文探讨了求解基环树上最大点权独立集的问题,提出两种解法:一是对环上每个点进行树形DP,再进行环形DP;二是将环断开,对断开的边分别进行树形DP。并通过代码详细展示了两种方法的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
题目大意:求基环树上的最大点权独立集。

题解:先考虑普通树的最大点权独立集:dp[i][0] 表示不选 i 点,以 i 为根的子树的最大点权独立集,dp[i][1] 表示选 i 点,以 i 为根的子树的最大点权独立集,然后简单树形DP,对于根结点取选和不选中的较大值。基环树,多了对环的处理。

思路一:对环上每一个点进行简单树形DP,然后将环上的点提出来做一遍环形DP,处理方法是将环断开,断开的边的两个点必然有一个不能选,枚举哪个点不选,然后做线性DP,结果取最大值。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
int nxt[2 * maxn],to[maxn * 2],head[maxn],cnt;
int a[maxn],n;
int f[maxn],dfn[maxn],sz,vis[maxn];
int v[maxn],top;
ll dp[maxn][2];
ll dpp[maxn][2];
void init() {
    sz = top = cnt = 0;
    fill(head,head + n + 1,-1);
}
void add(int u,int v) {
    to[cnt] = v;
    nxt[cnt] = head[u];
    head[u] = cnt++;
}
void getloop(int u,int fa) {
    dfn[u] = ++sz;
    bool t = false;
    for(int i = head[u]; i + 1; i = nxt[i]) {
        if(to[i] == fa && !t) {
            t = true;continue;
        }
        if(!dfn[to[i]]) {
            f[to[i]] = u;
            getloop(to[i],u);
        }
        else if(dfn[to[i]] < dfn[u]) continue;
        else if(dfn[to[i]] > dfn[u]) {
            v[++top] = to[i];
            vis[to[i]] = 1;
            for(int p = to[i]; p != u; p = f[p]) {
                v[++top] = f[p];
                vis[f[p]] = 1;
            }
        }
    }
}
void dfs(int u,int fa) {
    dp[u][0] = 0;
    dp[u][1] = a[u];
    for(int i = head[u]; i + 1; i = nxt[i]) {
        if(to[i] == fa || vis[to[i]]) continue;
        dfs(to[i],u);
        dp[u][0] += max(dp[to[i]][0],dp[to[i]][1]);
        dp[u][1] += dp[to[i]][0];
    }
}
int main() {
    scanf("%d",&n);
    int x,y;
    init();
    for(int i = 1; i <= n; i++) {
        scanf("%d%d",&x,&y);
        a[i] = x;
        add(i,y);add(y,i);
    }
    ll ans = 0;
    for(int i = 1; i <= n; i++) {
        if(!dfn[i]) {
            sz = top = 0;
            getloop(i,0);
            ll res = 0;
            for(int i = 1; i <= top; i++)
                dfs(v[i],0);
            dpp[1][0] = dp[v[1]][0];dpp[1][1] = dp[v[1]][1];
            for(int i = 2; i <= top; i++) {
                dpp[i][0] = dp[v[i]][0];
                dpp[i][1] = dp[v[i]][1];
                dpp[i][1] += dpp[i - 1][0];
                dpp[i][0] += max(dpp[i - 1][0],dpp[i - 1][1]);
            }
            res = max(res,dpp[top][0]);
            dpp[top][0] = dp[v[top]][0];dpp[top][1] = dp[v[top]][1];
            for(int i = top - 1; i >= 0; i--) {
                dpp[i][0] = dp[v[i]][0];
                dpp[i][1] = dp[v[i]][1];
               	dpp[i][1] += dpp[i + 1][0];
                dpp[i][0] += max(dpp[i + 1][0],dpp[i + 1][1]);
            }   
            res = max(res,dpp[1][0]);     
            ans += res;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

思路二:先将环断开,对于断开的那条边(u,v),保证根结点不选,分别以 u 为根和 以 v为根做树形DP,结果取最大值。

(一和二其实是一样的)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const ll inf = 1e15;
int nxt[2 * maxn],to[maxn * 2],head[maxn],cnt;
int a[maxn],n;
int vis[maxn];
int v[maxn],top,fa[maxn];
ll dp[maxn][2];
void init() {
    top = cnt = 0;
    fill(head,head + n + 1,-1);
}
void add(int u,int v) {
    to[cnt] = v;
    nxt[cnt] = head[u];
    head[u] = cnt++;
}
void dfs(int rt,int u,int s) {
    dp[u][0] = 0;
    dp[u][1] = a[u];
    vis[u] = 1;
    for(int i = head[u]; i + 1; i = nxt[i]) {
        if(to[i] == s || to[i] == rt) continue;
        else {
			dfs(rt,to[i],u);
	        dp[u][0] += max(dp[to[i]][0],dp[to[i]][1]);
	        dp[u][1] += dp[to[i]][0];
	    }
    }
}
ll solve(int x) {
	int rt = x;
	vis[rt] = 1;
	ll res = 0;
	while(!vis[fa[rt]]) {
		vis[fa[rt]] = 1;
		rt = fa[rt];
	}
	dfs(rt,rt,0);res = max(res,dp[rt][0]);
	dfs(fa[rt],fa[rt],0);res = max(res,dp[fa[rt]][0]);
	return res;
}
int main() {
    scanf("%d",&n);
    int x,y;
    init();
    for(int i = 1; i <= n; i++) {
        scanf("%d%d",&a[i],&fa[i]);
        add(fa[i],i);
    }
    ll ans = 0;
    for(int i = 1; i <= n; i++) {
    	if(!vis[i])
    		ans += solve(i);
	}
    printf("%lld\n",ans);
    return 0;
}

反思:写第一种方法wa了一晚上,结果是环形DP处理方法错了。对于这题在环型上DP,可以枚举固定一个点的状态,然后变成线性DP,和上面枚举哪个点不选的方法是等价的,第一种方法可以改成这样。

            dpp[1][0] = dp[v[1]][0];dpp[1][1] = dp[v[1]][1];
            for(int i = 2; i <= top; i++) {
                dpp[i][0] = dp[v[i]][0];
                dpp[i][1] = dp[v[i]][1];
                dpp[i][1] += dpp[i - 1][0];
                dpp[i][0] += max(dpp[i - 1][0],dpp[i - 1][1]);
            }
            res = max(res,dpp[top][0]);
            dpp[1][0] = dp[v[1]][0];dpp[1][1] = -1e15;
            for(int i = 2; i <= top; i++) {
                dpp[i][0] = dp[v[i]][0];
                dpp[i][1] = dp[v[i]][1];
               	dpp[i][1] += dpp[i - 1][0];
                dpp[i][0] += max(dpp[i - 1][0],dpp[i - 1][1]);
            }   
            res = max(res,dpp[top][1]); 

第二种方法是直接断链做树形DP,不能去枚举根结点选和不选做树形DP,因为树形DP是自底向上转移,根节点的状态不影响子节点的状态转移。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值