P2607 [ZJOI2008]骑士
知识点 : 树形DP
原题面
题目要求:
给定 多个联通块 , 每个联通块都为一 基环树
在各联通块 中选择一点集 , 使一条边的两端点不同时存在于其中
求 选择点集 权值的最大值
分析题意:
-
对于给定的多个联通块 , 由于其 最大权值和互不影响 ,
则可 对其分别进行考虑 . -
若联通块 中没有环时 :
则可随意选择 一点为根 , 并向下 \(dfs\) 深入-
问题变为 :
求一棵树 上点的点集 \(a\) ,
使 \(a_i != father[a_j] (i,j ∈ V)\)
求此点集 的最大权值和 -
对于一个点 ,
选择它 或 不选择它 , 只会对其 直接的儿子结点 产生影响
也就是说 ,
如果其直系儿子节点的状态已经确定 , 就可以推出 该点的状态 -
设 : \(f[i][1]\) 表示 选择第 \(i\) 个点后 , 其子树的最大权值和
\(f[i][0]\) 表示 , 不选择第 \(i\) 个点 , 其子树的最大权值和那么对于一个子树的根节点 \(i\) , 及其 直系儿子结点 集合 \(son\)
其状态转移方程为:
\(f[i][1] = value[i] + ∑(f[v][0]) (v∈ son)\) ;
\(f[i][0] = ∑max(f[v][1], f[v][0]) (v∈ son)\);
对于根节点 \(root\) 最后的状态有两种情况
答案只需在 \(f[root][1]\) 与 \(f[root][0]\) 取最大值即可 -
-
当联通块中存在环时 :
-
先 \(dfs\) 找到 联通块中的环
显然 , 环边上两点 不能同时存在与点集中
即: 使 一条环边上两点 不连通
则可以考虑 断环成树 , 即 规定一条环边 不存在 -
对断开边两端点 \(u1,v1\) 分别进行 \(2\) 中的算法,
求得以两端点 \(u1, v1\) 为根的时的贡献\(f[u1][0/1]\) 与 \(g[v1][0/1]\) -
显然 , 两点只能选择一个 存在于点集中
则 此联通块的贡献为 为: \(max(f[u1][0], g[v1][0])\) ;
相当于 强制 \(u1,v1\) 中一个不存在 , 或者两个都不存在 -
对每一个联通块 都进行上述算法, 求得权值和即可
-
#include<cstdio>
#include<ctype.h>
#include<cstring>
#define max(a,b) (a>b?a:b)
#define ll long long
const int MARX = 1e6+10;
//=============================================================
struct edge
{
int u,v,ne;
}e[MARX<<1];
int n,num, head[MARX],w[MARX];
int u1, v1, num1;//存 被断开的环边的信息
bool circle, used[MARX], vis[MARX], vis1[MARX], vis2[MARX];//找环 相关变量
ll ans, f[MARX][2], g[MARX][2];//dp
//=============================================================
inline int read()
{
int s=1, w=0; char ch=getchar();
for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
return s*w;
}
void add(int u,int v)
{
e[++num].u = u, e[num].v = v;
e[num].ne = head[u], head[u] = num;
}
void cover(int u)//将一联通块全部染色 , 表明已经全被使用过
{
used[u] = 1;
for(int i = head[u]; i; i = e[i].ne)
if(!used[e[i].v]) cover(e[i].v);
}
void find_circle(int u,int fat)//dfs找环
{
if(circle) return;
vis[u] = 1;
for(int i = head[u]; i; i = e[i].ne)
if(!vis[e[i].v]) find_circle(e[i].v,u);
else if(e[i].v != fat)//如果 一个点被重复经过
{
u1 = e[i].u, v1 = e[i].v, num1 = i;//找到了环
circle = 1;
return ;
}
}
void dfs1(int u)//树形DP
{
f[u][1] = w[u], vis1[u] = 1;
for(int i = head[u]; i; i = e[i].ne)
if(!vis1[e[i].v] && (i^1 != num1))
{
dfs1(e[i].v);//优先更新儿子结点
f[u][0] += max(f[e[i].v][0],f[e[i].v][1]);//更新子树根节点的值
f[u][1] += f[e[i].v][0];
}
}
void dfs2(int u)//树形DP,同上
{
g[u][1] = w[u], vis2[u] = 1;
for(int i = head[u]; i; i = e[i].ne)
if(!vis2[e[i].v] && (i^1 != num1))
{
dfs2(e[i].v);
g[u][0] += max(g[e[i].v][0],g[e[i].v][1]);
g[u][1] += g[e[i].v][0];
}
}
//=============================================================
signed main()
{
n = read();
for(int i = 1, v; i <= n; i ++)//建图
{
w[i] = read(), v = read();
add(i,v), add(v,i);
}
for(int i = 1; i <= n; i ++)
if(!used[i])
{
cover(i);//覆盖 联通块
circle = 0;
find_circle(i,0);//找环
dfs1(u1); dfs2(v1);
ans += max(f[u1][0], g[v1][0]);//取最大值为答案
}
printf("%lld",ans);
}