P2607 [ZJOI2008]骑士

知识点 : 树形DP

原题面

题目要求:

给定 多个联通块 , 每个联通块都为一 基环树
在各联通块 中选择一点集 , 使一条边的两端点不同时存在于其中
求 选择点集 权值的最大值

分析题意:

  1. 对于给定的多个联通块 , 由于其 最大权值和互不影响 ,
    则可 对其分别进行考虑 .

  2. 若联通块 中没有环时 :
    则可随意选择 一点为根 , 并向下 \(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]\) 取最大值即可

  3. 当联通块中存在环时 :

    • \(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);
}
posted @ 2019-10-18 21:21  Luckyblock  阅读(105)  评论(0编辑  收藏  举报