1. 程式人生 > >【BZOJ】1040: [ZJOI2008]騎士 環套樹DP

【BZOJ】1040: [ZJOI2008]騎士 環套樹DP

close opened != spl 分享 pan names 方案 blog

【題意】給定n個人的ai和bi,表示第i個人能力值為ai且不能和bi同時選擇,求能力值和最大的選擇方案。n<=10^6。

【算法】環套樹DP(基環樹)

【題解】n個點n條邊——基環森林(若幹環套樹子圖)。

若原圖是樹,經典DP做法:f[i][0/1]表示i點選或不選的最大能力值和,則f[i][0]=Σmax{f[j][0],f[j][1]},f[i][1]=Σf[j][0]+a[i],j=son[i]。

找環:dfs到訪問過的點,標記環上的一條邊。

破環:和普通樹上DP唯一的區別是,標記邊兩端不能同時為1,所以從兩端AB開始分別進行一次樹形DP,最後ans=max{f[A][0],f[B][0]}(這兩個f[]是兩次分別計算的結果)。

技術分享圖片
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=1000010;
int n,tot,first[maxn],A,B,a[maxn];
ll f[maxn][2];
bool vis[maxn],d[maxn*2];
struct edge{int v,from;}e[maxn*2];
void insert(int u,int v){tot++;e[tot].v=v;e[tot].from
=first[u];first[u]=tot;} void dfs(int x,int fa){ vis[x]=1; for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){ if(vis[e[i].v]){A=x;B=e[i].v;d[i]=d[i^1]=1;} else dfs(e[i].v,x); } } void dp(int x,int fa){ //printf("x=%d\n",x); f[x][0]=0;f[x][1]=a[x]; for(int i=first[x];i;i=e[i].from
)if(e[i].v!=fa&&!d[i]){ //printf("y=%d\n",e[i].v); dp(e[i].v,x); //printf("[%lld]\n",f[e[i].v][1]); f[x][0]+=max(f[e[i].v][0],f[e[i].v][1]); f[x][1]+=f[e[i].v][0]; } //printf("%lld %lld\n",f[x][0],f[x][1]); } int main(){ scanf("%d",&n); int v;tot=1; for(int i=1;i<=n;i++){ scanf("%d%d",&a[i],&v); insert(i,v);insert(v,i); } ll ans=0; for(int i=1;i<=n;i++)if(!vis[i]){ dfs(i,0); dp(A,0); ll sum=f[A][0]; dp(B,0); ans+=max(f[B][0],sum); } printf("%lld",ans); return 0; }
View Code

【BZOJ】1040: [ZJOI2008]騎士 環套樹DP