基环树动态规划

基环树DP

基环树定义

先回忆一下普通树结构有哪些特性:
1. N个点 N-1条边。
2. 没有父节点的节点称为根节点,除根节点外每个几点都有一个父节点。

基环树可以理解为:
1. 有N个点N条边
2. 在原来一颗树的基础上,添加一条边,那么就是基环树了

同理,在基环树上的环中,删掉一条边,那么就是一颗普通树了。
也正是利用这个特性,在基环树上进行动态规划。

基环树如何进行DP

还是考虑这条性质

在基环树上的环中,删掉一条边,那么就是一颗普通树了

也就是说,如果进行正常的DP,在环中是无法处理的,没法进行状态转移。
做法也很简单,我们把环拆开。假设连在一起的两个点为u和v,能够这样处理的原因在于,u和v存在限制关系(往往是不能同时选择),这样拆开后,只要分别讨论,选择u不选择v选择v不选择u两种情况,就可以得到最后正确的结果了。

例题 - BZOJ1040 骑士

分析

对于这道题来说,可以看出题目并不一定是一颗基环树,可能是基环树森林。做法比基环树多一步,就是对每一棵基环树进行操作,然后求和即可。

算法

存图采用的是链式向前星,由于题目中的树是无向边,所以要存两次,对于某条边来说,其正反(即由u到v和由v到u)边的序号的关系可以用,not_pass和 not_pass ^1 来表示。

存图后,首先进行dfs,找出基环树的环上一条边,然后设置好u和v以及not_pass(即要跳过的边)。然后开始进行dp操作,按照选择u不选择v和选择v不选择u分别进行DP,求得结果。

对于基环树的每棵树,都进行如上的操作。最后累加求和,即为答案。

代码

#include<bits/stdc++.h>
using namespace std;
const int nmax = 1000100;
typedef long long ll;
int head[nmax];
int tot = 0;
struct edge{
    int to,nxt;
    edge() {}
    edge(int _u, int _v){
        to = _v, nxt = head[_u];
    }
}e[nmax<<1];
bool visit[nmax];
int val[nmax];
int not_pass, p1,p2,n;
ll dp[nmax][2];
void add(int u, int v){
    e[tot] = edge(u,v);
    head[u] = tot++;
}
void init(){
    memset(head,-1,sizeof head);
    memset(visit,0,sizeof visit);
    tot = 0;
}
void dfs(int rt, int fa){
    visit[rt] = true;
    for(int i = head[rt];i!=-1;i = e[i].nxt){
        if(e[i].to == fa) continue;
        if(!visit[e[i].to]) dfs(e[i].to,rt);
        else{
            not_pass = i; // 标记边
            p1 = e[i].to; // 标记u
            p2 = rt;     // 标记v
        }
    }
}
void getdp(int rt, int fa){
    dp[rt][0] = 0, dp[rt][1] = val[rt];
    for(int i = head[rt]; i!=-1; i=e[i].nxt){
        if(e[i].to == fa) continue;
        if(i == not_pass || i == (not_pass^1)) continue; // 如果当前是标记边,就跳过
        getdp(e[i].to,rt);
        dp[rt][0] += max(dp[e[i].to][0],dp[e[i].to][1]);
        dp[rt][1] += dp[e[i].to][0];
    }
}
int main(){
    init();
    scanf("%d",&n);
    int v,t;
    for(int i = 1;i<=n;++i){
        scanf("%d %d",&v,&t);
        val[i] = v; add(i,t); add(t,i);
    }
    ll ans = 0;
    for(int i = 1;i<=n;++i){
        if(visit[i]) continue;
        dfs(i,-1);
        getdp(p1,-1);
        ll ans1 = dp[p1][0];
        getdp(p2,-1);
        ll ans2 = dp[p2][0];
        ll temp = max(ans1,ans2);
        ans += temp;
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值