【BZOJ1040】[ZJOI2008] 骑士(基环外向树DP)

点此看题面

大致题意: 给你一片基环外向树森林,如果选定了一个点,就不能选择与其相邻的节点。求选中点的最大权值和。


树形 D P DP DP

此题应该是 树形 D P DP DP 的一个升级版:基环外向树 D P DP DP

L i n k Link Link

树形 D P DP DP 详见博客 动态规划专题(二)——树形DP


什么是基环外向树森林

什么是 基环外向树

基环外向树,一般指一张 点数与边数相等 的联通图,此时必然存在一个环,若把这个环当成一个节点,则原图就形成了一棵树。

什么是 基环外向树森林

一张由若干个基环外向树组成的图(此时 点数仍然等于边数),就是基环外向树森林


基环外向树 D P DP DP

那么,基环外向树 D P DP DP 应该怎么写呢?

不难发现,对于某一棵基环外向树,只要去掉环上的一条边,它就成为一棵普通的树了。

所以,我们就随便去掉环上的一条边。

然后分别以 这条边连接的两个端点 为根,跑 树形 D P DP DP

这棵基环外向树的贡献就是两次 D P DP DP结果的较大值。

要注意的是,由于这两个端点被一条边连接了,因此这两个端点不能同时选择。

具体内容还是看代码吧。


代码
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define N 1000000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].Exist=1)
using namespace std;
LL n,rt,Size=1,ee=0,lnk[N+5],Val[N+5];
struct edge
{
	LL to,nxt,Exist;
}e[2*N+5];
class FIO
{
	private:
		#define Fsize 100000
		#define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
		#define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
		LL f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
	public:
		FIO() {FinNow=FinEnd=Fin;}
		inline void read(LL &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));x*=f;}
		inline void read_char(char &x) {while(isspace(x=tc()));}
		inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
		inline void write(LL x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
		inline void write_char(char x) {pc(x);}
		inline void write_string(string x) {register LL i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
		inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
class Class_FindCircle//找环
{
	public:
		LL L,R,Edge,vis[N+5];
		inline void Solve(LL x,LL lst)
		{
			register LL i;
			for(vis[x]=1,i=lnk[x];i;i=e[i].nxt)
			{
				if(!(e[i].to^lst)) continue;
				vis[e[i].to]?(void)(L=x,R=e[i].to,Edge=i):Solve(e[i].to,x);//如果已经访问过这条边连接的另一个节点,就说明找到了环上的一条边,将其存储下来
			}
		}	
}FindCircle;
class Class_TreeDP//树形DP
{
	private:
		LL f[N+5][2];
	public:
		inline void DP(LL x,LL lst,LL CanNot)
		{
			register LL i;
			for(f[x][0]=0,f[x][1]=x^CanNot?Val[x]:0,i=lnk[x];i;i=e[i].nxt)
			{
				if(!(e[i].to^lst)||!e[i].Exist) continue;
				DP(e[i].to,x,CanNot),f[x][0]+=max(f[e[i].to][0],f[e[i].to][1]),f[x][1]+=f[e[i].to][0];//状态转移
			}
		}
		inline LL GetAns(LL x)
		{
			return max(f[x][0],f[x][1]);
		}
}TreeDP;
int main()
{
    register LL i,x,y,ans=0,res1,res2;
    for(F.read(n),i=1;i<=n;++i) F.read(Val[i]),F.read(x),add(x,i),add(i,x);
    for(i=1;i<=n;++i)
    {
    	if(FindCircle.vis[i]) continue;//如果这个节点所在的基环外向树已经访问过了,就跳过当前节点
    	FindCircle.Solve(i,0),e[FindCircle.Edge].Exist=e[((FindCircle.Edge-1)^1)+1].Exist=0,//找到环上的一条边,并将这条边删去
		TreeDP.DP(FindCircle.L,0,FindCircle.R),res1=TreeDP.GetAns(FindCircle.L),TreeDP.DP(FindCircle.R,0,FindCircle.L),res2=TreeDP.GetAns(FindCircle.R),//分别以两个端点为根,跑树形DP
		ans+=max(res1,res2);//这棵基环外向树对答案的贡献就是两次DP的结果的较大值
	}
    return F.write(ans),F.end(),0;
}          
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用dp来解决这个问题,具体来说,我们可以将这个图看作是一棵,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值