BZOJ3037 创世纪[基环树DP]
实际上基环树DP的名字是假的。。
这个限制关系可以看成每个点有一条出边,所以就是一个内向基环树森林。
找出每个基环树的环,然后对于树的部分,做DP,设状态选或不选为$f_{x,0/1}$,则
$f_{x,0}=\sum\limits_{y\in son_x} \max\{f_{y,0},f_{y,1}\}$
$f_{x,1}=\sum\limits_{y\in son_x} \max\{f_{y,0},f_{y,1}\}+[全选了f_{y,1}?]\sum\limits_{y\in son_x} \max\{f_{y,0}-f_{y,1}\}$
这个DP很简单,但是在环上很难处理,因为每个点取不取既和环上前一个点有关也和儿子有关,这个环上的点DP转移是有后效性的。
采用基环树DP另一个常用的手段:断环成树,树形容易处理,避免后效性。断环时,应当注意断开的环上两个点相互影响的关系。
比如此题,每一个点可能会影响后一个点的选择,分两种情况:此点不影响下一个点,则断开后,以此点为根做树形DP,那么两点互不影响,题目条件都成立。
若考虑有影响,则需要这个点不选,让下一个点可以随便选儿子的两个状态较大值而不用担心没有指向他的不选的点。
综上,断边后做两次DP即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #define mst(x) memset(x,0,sizeof x) 8 #define dbg(x) cerr << #x << " = " << x <<endl 9 #define dbg2(x,y) cerr<< #x <<" = "<< x <<" "<< #y <<" = "<< y <<endl 10 using namespace std; 11 typedef long long ll; 12 typedef double db; 13 typedef pair<int,int> pii; 14 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 15 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 16 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 17 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 18 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 19 template<typename T>inline T read(T&x){ 20 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 21 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 22 } 23 const int N=1e6+7; 24 struct thxorz{ 25 int head[N],nxt[N<<1],to[N<<1],tot; 26 thxorz(){tot=1;} 27 inline void add(int x,int y){ 28 to[++tot]=y,nxt[tot]=head[x],head[x]=tot; 29 to[++tot]=x,nxt[tot]=head[y],head[y]=tot; 30 } 31 }G; 32 int n; 33 #define y G.to[j] 34 int vis[N],to,rt,flag,ans,res,ban; 35 void dfs(int x){//dbg(x); 36 vis[x]=1; 37 for(register int j=G.head[x];j;j=G.nxt[j])if(!(j&1)){ 38 if(vis[y])return rt=x,to=y,ban=j,void(); 39 else dfs(y); 40 } 41 } 42 int f[N][2]; 43 void dp1(int x,int fa){ 44 vis[x]=1;int chosen=1;f[x][1]=1; 45 for(register int j=G.head[x];j;j=G.nxt[j])if(y^fa&&j^ban&&j^(ban^1)){ 46 dp1(y,x); 47 if(f[y][0]<f[y][1])f[x][0]+=f[y][1],f[x][1]+=f[y][1]; 48 else f[x][0]+=f[y][0],f[x][1]+=f[y][0],chosen=0; 49 } 50 if(chosen){ 51 int tmp=-N; 52 for(register int j=G.head[x];j;j=G.nxt[j])if(y^fa&&j^ban&&j^(ban^1))MAX(tmp,f[y][0]-f[y][1]); 53 f[x][1]+=tmp; 54 }//dbg2(x,chosen); 55 } 56 void dp2(int x,int fa){ 57 int chosen=1;f[x][0]=0,f[x][1]=1; 58 for(register int j=G.head[x];j;j=G.nxt[j])if(y^fa&&j^ban&&j^(ban^1)){ 59 dp2(y,x); 60 if(f[y][0]<f[y][1])f[x][0]+=f[y][1],f[x][1]+=f[y][1]; 61 else f[x][0]+=f[y][0],f[x][1]+=f[y][0],chosen=0; 62 } 63 if(chosen&&x!=to){ 64 int tmp=-N; 65 for(register int j=G.head[x];j;j=G.nxt[j])if(y^fa&&j^ban&&j^(ban^1))MAX(tmp,f[y][0]-f[y][1]); 66 f[x][1]+=tmp; 67 } 68 } 69 #undef y 70 71 int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout); 72 read(n); 73 for(register int i=1,x;i<=n;++i)read(x),G.add(i,x); 74 for(register int i=1;i<=n;++i,res=0)if(!vis[i]){ 75 dfs(i); 76 dp1(rt,0);//不考虑断环之后根对断点有影响 77 res=_max(f[rt][0],f[rt][1]); 78 dp2(rt,0);//考虑断环之后根对断点有影响(即不选根) 79 MAX(res,f[rt][0]); 80 ans+=res; 81 } 82 printf("%d\n",ans); 83 return 0; 84 }
总结:基环树另一种常见做法:断环成树,考虑影响,做两次树形DP。