【题解】BZOJ 1040 [ZJOI2008] 骑士

12 篇文章 0 订阅

传送门

Description D e s c r i p t i o n

在基环树上求解最大点独立集问题。

有点学术啊。那我先来解释一下吧。

基环树

基环树 顾名思义,就是基于一个环上的树,也就是一个树中有一个环。由于原题中把骑士看成结点,憎恨关系看成边之后就得到了一个有 n n 个结点, n 条边的,我们知道由 n n 个结点, n1 条边组成的连通图是一棵树。那么拥有 n n 个结点和 n 条边的连通图就是一棵基环树。这样一来,原题的图就可以抽象成一个基环树森林

最大点独立集

一个图的点独立集是原图总点集的一个子集,且点独立集中任意两个顶点均不相邻。简单点来说就是从图中挑点,所取的点两两不相邻。

图的最大点独立集就是顶点数最多的点独立集。

但是本题是带点权的,这样一来就要稍微更改一下定义,我们定义图的最大带权点独立集就是总权重(集合中所有点的权重的和)最大的点独立集。

Solution S o l u t i o n

假如这是个森林的话就很简单,就是 没有上司的舞会

这里面详细介绍了怎样解决树上的最大点独立集问题,这里就不再赘述。

但是带权的话,状态转移方程就要修改一下:

我们定义 d(i,0) d ( i , 0 ) 表示在以 i i 为根的子树中,不取 i 所能达到的最大权重。

相应的, d(i,1) d ( i , 1 ) 表示以 i i 为根的子树中,取 i 所能达到的最大权重。

d(u,0)=(u,v)Emax{d(v,1),d(v,0)}d(u,1)=(u,v)Ed(v,0) d ( u , 0 ) = ∑ ( u , v ) ∈ E m a x { d ( v , 1 ) , d ( v , 0 ) } d ( u , 1 ) = ∑ ( u , v ) ∈ E d ( v , 0 )

由于上面的转移方程是对付树的,基环树又是树的特殊变种(基环树不是树)。考虑怎样对基环树进行处理使之可以用上面的方法解决。

考虑到基环树删去环上的一条边就变成了一棵树(含有 n n 个结点及 n1 条边且保证连通)。

我们就可以设计出一个算法:找到这个基环树中的环,并找到其上任意一条边 (u,v) ( u , v ) (所有边的地位等同),然后把它“删掉”(在dp过程中不经过这条边),让它变成一棵树,接下来我们考虑添加这条边对结果的影响。

很显然,多了这条边, u u v 不可以同时选取,这样子就会有三种情况:

  • u u , 不选 v
  • v v ,不选 u
  • 既不选 u u 也不选 v

但是这样是很难处理的。我们可以考虑合并成两种情况:

  • 不选 u u , v 随意
  • 不选 v v , u 随意

这样就可以解决问题了。

我们在solve完 u u 结点之后,保存 p=d(u,0) ,再solve v v 结点,保存 q=d(v,0) 再取 max{p,q} max { p , q } 加入 Ans A n s 即可。这样一来就保证了 u u v 不会同时选中,也保证了解的最优性。

Code C o d e

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 1000010
#define max(x, y) ((x) > (y) ? (x) : (y))
int value[MAXN], a, b;
long long f[MAXN][2];
struct edgeType {
    int to, next;
} edge[MAXN << 1];
int head[MAXN], vis[MAXN], n, s, x, y, cut;
void addEdge(int from, int to) {
    static int cnt = 0;
    edge[cnt] = (edgeType){to, head[from]};
    head[from] = cnt++;
}
void findCycle(int u, int p) {
    vis[u] = 1;
    for (int i = head[u]; ~i; i = edge[i].next) {
        if ((i ^ 1) == p) continue;
        int v = edge[i].to;
        if (vis[v]) {
            x = u, y = v;
            cut = i;
            continue;
        }
        findCycle(v, i);
    }
}
void solve(int u, int p) {
    f[u][0] = 0;
    f[u][1] = value[u];
    for (int i = head[u]; ~i; i = edge[i].next) {
        if ((i ^ 1) == p) continue;
        if (i == cut || (i ^ 1) == cut)
            continue;
        int v = edge[i].to;
        dfs(v, i);
        f[u][1] += f[v][0];
        f[u][0] += max(f[v][1], f[v][0]);
    }
}
int main() {
    memset(head, -1, sizeof head);
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &a, &b);
        addEdge(i, b);
        addEdge(b, i);
        value[i] = a;
    }
    long long ans = 0;
    for (int i = 1; i <= n; i++) {
        if (vis[i]) continue;
        findCycle(i, -2);
        solve(x, -1);
        long long temp = f[x][0];
        solve(y, -1);
        temp = max(temp, f[y][0]);
        ans += temp; 
    }
    printf("%lld", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值