zoukankan      html  css  js  c++  java
  • 【BZOJ1040】[ZJOI2008] 骑士(基环外向树DP)

    点此看题面

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

    树形(DP)

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

    什么是基环外向树森林

    什么是 基环外向树

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

    什么是 基环外向树森林

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

    基环外向树(DP)

    那么,基环外向树(DP) 应该怎么写呢?

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

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

    然后分别以 这条边连接的两个端点 为根,跑 树形(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;
    }          
    
  • 相关阅读:
    Java 正则表达式
    连续子数组最大和
    背包问题
    二叉树的数组存储
    各种鸟
    mac关闭和开启启动声
    关于栈和队列随想
    linux主机名 hostname
    mysql创建新用户并且授权远程访问
    关于linux的用户
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ1040.html
Copyright © 2011-2022 走看看