bzoj1040&CodeVS1423 骑士

http://www.lydsy.com/JudgeOnline/problem.php?id=1040

http://codevs.cn/problem/1423/

前言:这是第一次发bzoj题解,纪念一下。

noip在即,大家都要加油哦。

题意:有N个点,对于每个点都给出另一个点,表示它们之间有一条无向边。求最大带权独立子集。

这整个图不一定联通,但是每一个联通子图都有一个性质:边数与点数对应相等。每个联通子图可以分开处理。

最大(带权)独立子集是NP-hard问题,但是由于这里图的特殊性,可以用动态规划来解决。

对于一棵树的最大(带权)独立子集,任选一点为根,令f[i][0]和f[i][1]分别表示第i个点不选和选,该子树能取得的最大权值和。

则f[i][0]=sigma{max(f[j][0],f[j][1])},f[i][1]=w[i]+sigma{f[j][0]},其中w[i]为第i个点的权值,j是i的儿子。

对于一个边数和点数的联通图,可以考虑删去适当的一条边,使其成为一棵树。

设删掉的边的两点为x和y,分别令它们为根做树形DP,因为两者最多取其一,所以取f[x][0]和f[y][0]的较大值计入该联通子图的贡献。

代码:(拆边写的很难看,并且因为这个WA了三次,每次都90分,分别错的是第6,5,9个数据点0、

#include<cstdio>
#include<iostream>
#include<cstring>
#define rpt(i,l,r) for(i=l;i<=r;i++)
#define rpd(i,r,l) for(i=r;i>=l;i--)
#define maxn 10000005
#define mx(a,b) (a)>(b)?(a):(b)
using namespace std;
int l[maxn],w[maxn],s[maxn]={0},t[maxn<<1],b[maxn]={0},fa[maxn];
int n,i,kk,x,y;
long long f[maxn][2];
long long p,q,res=0;
void findxy(int e){
                   int i;
                   rpt(i,s[e-1]+1,s[e]) if(b[t[i]]==0&&t[i]){
                                                       b[t[i]]=1;
                                                       fa[t[i]]=e;
                                                       int tt=t[i];
                                                       findxy(t[i]);
                                                       fa[tt]=0;
                   }
                   else if((fa[e]!=t[i]||(l[l[t[i]]]==t[i]&&l[l[e]]==e))&&kk){
                                            kk=0;
                                            x=e;
                                            y=t[i];
                                            int j;
                                            rpt(j,s[t[i]-1]+1,s[t[i]]) if(t[j]==e){
                                                                                   t[j]=0;
                                                                                   break;
                                            }
                                            t[i]=0;
                   }
}
void dp(int e){
               int i;
               f[e][0]=0;
               f[e][1]=w[e];
               rpt(i,s[e-1]+1,s[e]) if(t[i]&&fa[e]!=t[i]){
                                                          fa[t[i]]=e;
                                                          dp(t[i]);
                                                          f[e][0]+=mx(f[t[i]][0],f[t[i]][1]);
                                                          f[e][1]+=f[t[i]][0];
                                                          fa[t[i]]=0;
               }
}
int main(){
    scanf("%d",&n);
    rpt(i,1,n){
               scanf("%d%d",&w[i],&l[i]);
               s[i]++;
               s[l[i]]++;
    }
    rpt(i,1,n) s[i]+=s[i-1];
    rpd(i,n,1) s[i]=s[i-1];
    rpt(i,1,n){
               t[++s[i]]=l[i];
               t[++s[l[i]]]=i;
    }
    rpt(i,1,n) if(b[i]==0){
                           kk=1;
                           b[i]=1;
                           findxy(i);
                           dp(x);
                           p=f[x][0];
                           dp(y);
                           q=f[y][0];
                           res+=mx(p,q);
    }
    printf("%lld\n",res);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值