[2019.3.5]BZOJ1040 [ZJOI2008]骑士
我们把每一个骑士看做一个点,和他厌恶的骑士之间连一条双向边。
那么问题变为求基环树森林上的最大权独立集。
我们先考虑一颗基环树上的情况。
下图是一棵基环树,蓝边为环上的边。
发现每一块连在一起的非环边(红边)构成一棵树,而且明显每一棵树中有且仅有一个环上的点。
那么我们把每棵树中环上的点作为根,设\(f_{i,0/1}\)表示\(i\)及其子树中,点\(i\)选/没选的最大权独立集的权值。
\(f\)的求得就是一个普通的树形动规问题。
于是我们考虑环上的点。
将\(f_{i,1}\)看作选择点\(i\)的权值,\(f_{i,0}\)看作不选点\(i\)的权值。
于是我们要求环上的最大权独立集。
一个简单的环形动规问题。
任选环上一点\(s\)开始dp。
设\(dp_{i,0/1,0/1}\)表示考虑\(s\)到\(i\)的路径上,\(s\)选/不选,点\(i\)选/不选,得到的最大权值。
那么
\(dp_{s,0,0}=f_{s,0}\)
\(dp_{s,1,1}=f_{s,1}\)
\(dp_{s,0,1}=dp_{s,1,0}=-\infty\)
设\(i\)的上一个点为\(j\),那么
\(dp_{i,0,0}=max(dp_{j,0,1},dp_{j,0,0})+f_{i,0}\)
\(dp_{i,1,0}=max(dp_{j,1,1},dp_{j,1,0})+f_{i,0}\)
\(dp_{i,0,1}=dp_{j,0,0}+f_{i,1}\)
\(dp_{i,1,1}=dp_{j,1,0}+f_{i,1}\)
设环的终点为\(t\),则答案=\(max(dp_{t,1,0},dp_{t,0,1},dp_{t,0,0})\)
所有基环树的答案总和就是基环树森林的答案。
code:
#include<bits/stdc++.h>
#define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
using namespace std;
const long long FINF=-1e16;
struct edge{
int t,nxt;
}e[2000010];
int n,w[1000010],u,cnt,be[1000010],d[1000010],tg[1000010],vis[1000010];
long long f[1000010][2],dp[1000010][2][2],ans;
char buf[1<<21],*p1=buf,*p2=buf;
queue<int>q;
void add(int x,int y){
e[++cnt].t=y,e[cnt].nxt=be[x],be[x]=cnt,++d[y];
}
void bfs(){
for(int i=1;i<=n;++i)d[i]==1?q.push(i),tg[i]=1:0;
while(!q.empty()){
u=q.front(),q.pop();
for(int i=be[u];i;i=e[i].nxt)--d[e[i].t]==1?q.push(e[i].t),tg[e[i].t]=1:0;
}
}
void Merge(int x,int y){
f[x][0]+=max(f[y][0],f[y][1]);
f[x][1]+=f[y][0];
}
void dfs(int x){
f[x][1]=w[x];
for(int i=be[x];i;i=e[i].nxt)!f[e[i].t][1]&&tg[e[i].t]?dfs(e[i].t),Merge(x,e[i].t),0:0;
}
void Upd(int x,int y){
dp[x][1][0]=max(dp[y][1][1],dp[y][1][0])+f[x][0];
dp[x][0][0]=max(dp[y][0][1],dp[y][0][0])+f[x][0];
dp[x][1][1]=dp[y][1][0]+f[x][1];
dp[x][0][1]=dp[y][0][0]+f[x][1];
}
void DP(int x){
int tag=1;
vis[x]=1;
for(int i=be[x];i&&tag;i=e[i].nxt)!tg[e[i].t]&&!vis[e[i].t]?Upd(e[i].t,x),DP(e[i].t),tag=0:0;
ans+=tag*max(dp[x][1][0],max(dp[x][0][1],dp[x][0][0]));
}
void scan(int &x){
x=0;
char c=getchar();
while(!isdigit(c))c=getchar();
while(isdigit(c))x=x*10+c-'0',c=getchar();
}
int main(){
scan(n);
for(int i=1;i<=n;++i)scan(w[i]),scan(u),add(i,u),add(u,i);
bfs();
for(int i=1;i<=n;++i)!tg[i]?dfs(i),0:0;
for(int i=1;i<=n;++i)!tg[i]&&!vis[i]?dp[i][1][1]=f[i][1],dp[i][0][0]=f[i][0],dp[i][1][0]=dp[i][0][1]=FINF,DP(i),0:0;
printf("%lld",ans);
return 0;
}