【树DP】【基环树】[ZJOI2008][HYSBZ/BZOJ1040]骑士
题目链接
分析
看穿题目
如果一个骑士痛恨另一个骑士,就在两个骑士直接连接一条无向边。题目就是要求图中的最大权值独立集。
实现方法
如果这道题的图是一棵树(或森林)的话,显然可以用树形动态规划解决。
令
但是这道题的图并不是树,那怎么办呢?
这道题的图中,每一个连通块一定是“基环树”,即每个连通块有且只有一个环(边数=点数)。
那对于每个连通块,我们只需要找出环,然后在环上随意切掉一条边,这个连通块就成为了一棵树,可以做树dp了。
显然,这条边连接的两个点是不能同时被选择的,怎么限制这个条件呢?
我们只需要对这个连通块做两次dp,以这两个点为分别根,且强制不选择为根的那个点即可。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 1000000
int wt[MAXN+10],fa[MAXN+10],n,st,ed,fa2[MAXN+10];
long long ans,gs[MAXN+10],s[MAXN+10],f[MAXN+10];
bool vis[MAXN+10];
struct node{
int v;
bool f,vis;
node *next,*back;
}edge[MAXN*2+10],*adj[MAXN+10],*ecnt=&edge[0],*cut;
void addedge(int u,int v){
node *p=++ecnt;
p->v=v;
p->next=adj[u];
adj[u]=p;
p=p->back=++ecnt;
p->v=u;
p->next=adj[v];
adj[v]=p;
p->back=ecnt-1;
}
void Read(int &x){
char c;
while((c=getchar())&&c!=EOF)
if(c>='0'&&c<='9'){
x=c-'0';
while((c=getchar())&&c>='0'&&c<='9')
x=x*10+c-'0';
ungetc(c,stdin);
return;
}
}
void read(){
int t;
Read(n);
for(int i=1;i<=n;i++){
Read(wt[i]),Read(t);
addedge(i,t);
}
}
void dfs(int u){
vis[u]=1;
for(node *p=adj[u];p;p=p->next){
if(!p->vis){
p->vis=p->back->vis=1;
if(vis[p->v]){
st=p->v,ed=u;
cut=p;
continue;
}
fa[p->v]=u;
dfs(p->v);
}
}
}
void dfs2(int u){
gs[u]=s[u]=0;
for(node *p=adj[u];p;p=p->next)
if(!p->f&&p->v!=fa2[u]){
fa2[p->v]=u;
dfs2(p->v);
}
f[u]=max(gs[u]+wt[u],s[u]);
s[fa2[u]]+=f[u];
gs[fa2[fa2[u]]]+=f[u];
}
void solve(){
int i;
long long mx;
for(i=1;i<=n;i++){
mx=0;
if(!vis[i]){
dfs(i);
cut->f=cut->back->f=1;
fa2[st]=0;
dfs2(st);
mx=max(mx,s[st]);
fa2[ed]=0;
dfs2(ed);
mx=max(mx,s[ed]);
cut->f=cut->back->f=0;
}
ans+=mx;
}
}
int main()
{
read();
solve();
printf("%lld\n",ans);
}