bzoj1040 骑士

每个骑士恨且仅恨一个人,不能与他共同出战,每个骑士还有一个武力值,问不冲突的最大出战武力值是多少?

关键词:基环森林(每棵树上有且仅有一个环)的最大权值独立集

建图时每个骑士指向他恨的人,该图的特点:仅有一条出边。

结论:每个点有且仅有一条出边的连通图的底图(有向边改为无向边)是基环树。

证明:引理:n个点构成无向连通图,则边数-点数+1=最小环数(每个环内无其他环)。该结论是经验结论,例如树有n-1个点,则无环,n点n边有一环。

对于本题,n个点引出n条边,有且仅有一个环形成。结论成立。

对于基环树,仍有O(n)的dp算法解决最大权值独立集的问题。

1.先找到树中的环,任选一条边,标记。

2.切掉该边,分别将该边的两端点作为根,进行树dp独立集寻找,取不含两端点的dp值的最大值即可。因为不含两端点则该边作用无效,可以切去该边。由于两者不能同时含有,分别不含两端点已经覆盖所有情况。

#include<stdio.h>
#include<iostream>
#include<string.h>
#define ll long long
#define maxn 1000010
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;

int n;
ll w[maxn];
struct Edge{
    int to,next;
}edge[maxn*2];
int head[maxn],tot;
int vis[maxn],root,_root,cut;//在找环时使用,判断是否遍历过
ll maxc[maxn][3],ans;

void add(int u,int v){
    edge[tot].to=v,edge[tot].next=head[u],head[u]=tot++;
}

void find_onecircle(int u,int fa){//找环并顺便将所有的点标记
    vis[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa) continue;
        if(!vis[v]) find_onecircle(v,u);
        else{ root=v,_root=u,cut=i; }
    }
}

void tree_maxind(int u,int fa){
    maxc[u][1]=w[u],maxc[u][0]=0;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(i==cut||i==(cut^1)||v==fa) continue;
        tree_maxind(v,u);
        maxc[u][1]+=maxc[v][0];
        maxc[u][0]+=max(maxc[v][0],maxc[v][1]);
    }
}

ll onecircle_tree_maxind(int u){//在u所在基环树中求最大独立点集
    ll tmp=0;
    find_onecircle(u,-1);//在树中找到一个环,并切掉一条边
    tree_maxind(root,-1);
    tmp=maxc[root][0];
    tree_maxind(_root,-1);
    tmp=max(tmp,maxc[_root][0]);
    return tmp;
}

int main(){
    scanf("%d",&n);
    mem(head,-1),ans=0,tot=0;
    mem(vis,0),mem(maxc,0);
    for(int i=1;i<=n;i++){
        int a,b;
        scanf("%lld%d",&a,&b);
        w[i]=a;
        add(i,b),add(b,i);
    }
    for(int i=1;i<=n;i++){
        if(vis[i]) continue;
        ans+=onecircle_tree_maxind(i);
    }
    printf("%lld\n",ans);
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值