【BZOJ】1040: [ZJOI2008]騎士 環套樹DP
阿新 • • 發佈:2018-01-17
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[]是兩次分別計算的結果)。
![技術分享圖片](/img/jia.gif)
#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].fromView Code=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; }
【BZOJ】1040: [ZJOI2008]騎士 環套樹DP