【BZOJ】1040: [ZJOI2008]骑士 环套树DP
【题意】给定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; }