[BZOJ1040][[ZJOI2008]骑士][基环外向树+树形DP]

50 篇文章 0 订阅
3 篇文章 0 订阅

[BZOJ1040][[ZJOI2008]骑士][基环外向树+树形DP]

题目大意:

给定一个 N<=1000000 个点的基环外向森林,求带权的最大独立集。

思路:

首先题目中看上去给出的是有向边但实际上是无向边,因为 v 不会和u在一起那么 u 也不会和v在一起。

这道题一开始我以为只是一棵普通的树,那这样直接树形 dp 就好了,用 f[u][1],f[u][0] 表示 u 点选中或者不选的最大值,不妨设v u 的儿子,那么状态转移方程显然就是:

f[u][0]=Max(f[v][1],f[v][0])

f[u][1]=f[v][0]

再读题发现自己傻逼了,题目总共给出了 N 个点N条边,显然形成了一棵基环外向树,但这样其实还是很好搞,我们 dfs 或者 bfs 找到树中的环把环的两头断开,显然两头不能同时选因为它们是联通的,那么我们分别让其中一个选,另一个不选,或者两个都不选分别搞一下树形 dp ,然后在结果中取最大值就好了。

再打完发现自己又傻逼了,显然所有骑士并不一定会连在一起,肯定是只有一棵基环外向树,另外有或没有很多棵普通的树。但其实还是一样搞,在最外层枚举一下每个点有没有 vis 过就好了,然后把每棵树的贡献加入 ans

断边可以直接把边设为 false ,但边是双向边而前向星的存法只能一条边表示一个方向,但(两级反转)加边的时候是两个方向的边是同时加入的,所有一条边的编号是 id ,另一条边的编号就是 id1 ,处理起来很方便。

代码:
#include <bits/stdc++.h>
typedef long long ll;
const int Maxn = 1000010;
using namespace std;

inline ll Max(const ll &a, const ll &b) {
    return a > b ? a : b;
}
namespace IO {
    inline char get(void) {
        static char buf[1000000], *p1 = buf, *p2 = buf;
        if (p1 == p2) {
            p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
            if (p1 == p2) return EOF;
        }
        return *p1++;
    }
    inline void read(ll &x) {
        x = 0; static char c;
        for (; !(c >= '0' && c <= '9'); c = get());
        for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get());
    }
};
ll head[Maxn], sub;
struct Edge {
    ll to, nxt;
    bool ok;
    Edge(void) {}
    Edge(const int &to, const int &nxt) : to(to), nxt(nxt) { ok = 1; }
} edge[Maxn << 1];
inline void add(int a, int b) {
    edge[sub] = Edge(b, head[a]), head[a] = sub++;
}
ll n, val[Maxn], U, V;
ll f[2][2][Maxn], ans, fa[Maxn];
bool vis[Maxn], vis1[Maxn];
inline void Dp(ll u, ll w) {
    f[w][1][u] = val[u];
    vis[u] = 1;
    for (int i = head[u], v; ~i; i = edge[i].nxt) {
        v = edge[i].to;
        if (!edge[i].ok || f[w][1][v]) continue;
        Dp(v, w);
        f[w][0][u] += Max(f[w][1][v], f[w][0][v]);
        f[w][1][u] += f[w][0][v];
    }
    vis[u] = 0;
}
queue<int> qu;
inline ll work(ll u) {
    vis1[u] = 1;
    U = V = -1;
    qu.push(u);
    bool ok = false;
    while (!qu.empty()) {
        int k = qu.front(); qu.pop();
        for (int i = head[k], v; ~i; i = edge[i].nxt) {
            v = edge[i].to;
            if (!vis1[v] || fa[k] != v) {
                if (vis1[v]) {
                    edge[i].ok = edge[i ^ 1].ok = 0;
                    U = k, V = v;
                    ok = true;
                    break;
                 }
                 vis1[v] = 1;
                 fa[v] = k;
                 qu.push(v);
             }
         }
         if (ok) {
            while (!qu.empty()) qu.pop();
         }
    }

    //cout << U << ' ' << V << endl;
    if (!ok) return Dp(u, 0), Max(f[0][0][u], f[0][1][u]);
    else return Dp(U, 0), Dp(V, 1), Max(f[0][0][U], f[1][0][V]);
}
int main(void) {
    //freopen("in.txt", "r", stdin);
    IO::read(n);
    memset(head, -1, sizeof head);
    for (ll i = 1, fa; i <= n; i ++) {
        IO::read(val[i]); IO::read(fa);
        add(fa, i), add(i, fa);
    }
    for (int i = 1; i <= n; i++)
        if (!f[0][1][i]) {
            ans += work(i);
        }

    cout << ans << endl;
    return 0;
}

完。

By g1n0st

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值