BZOJ 1040: [ZJOI2008]骑士
题目大意:
给定基环外向树森林,每个点有点权,一条边连接的两个点不能同时选取,问选取的点权和最大。
题解:
如果是一棵树,有一个显然树形DP。
如果是基环外向树,那么先在每棵树上DP再在环上DP。
然而这个做法比较麻烦。
于是我们断开环上的一条边,强制一个点不选,就变成了一棵树。
跑两遍树形DP即可
代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int flag,n,x,l,r,cnt,tot,last[2000005],vis[2000005],s[2000005]; long long ans,f[2000005][2],val[2000005]; struct node{ int to,next; }e[2000005]; void add(int a,int b){ e[++cnt].to=b; e[cnt].next=last[a]; last[a]=cnt; } void dfs(int x,int fa){ vis[x]=1; for (int i=last[x]; i!=-1 && (!flag); i=e[i].next){ int V=e[i].to; if (V!=fa){ if (vis[V]){ l=x,r=V; s[i]=s[i^1]=-1; flag=1; break; } dfs(V,x); } } } void tree_dp(int x,int fa,int ed){ vis[x]=1; if (x!=ed) f[x][1]=val[x]; else f[x][1]=0; f[x][0]=0; for (int i=last[x]; i!=-1; i=e[i].next){ int V=e[i].to; if (V==fa) continue; if (s[i]==-1) continue; tree_dp(V,x,ed); f[x][0]+=max(f[V][0],f[V][1]); f[x][1]+=f[V][0]; } } int main(){ cnt=-1; memset(last,-1,sizeof(last)); int n; scanf("%d",&n); for (int i=1; i<=n; i++){ int x; scanf("%lld%d",&val[i],&x); add(x,i); add(i,x); } for (int i=1; i<=n; i++) if (!vis[i]){ flag=0; dfs(i,0); long long maxx=0; tree_dp(l,0,r); maxx=max(f[l][0],f[l][1]); tree_dp(r,0,l); maxx=max(maxx,max(f[r][0],f[r][1])); ans+=maxx; } printf("%lld\n",ans); return 0; }