【BZOJ1040】[ZJOI2008] 騎士(基環外向樹DP)
阿新 • • 發佈:2018-12-12
大致題意: 給你一片基環外向樹森林,如果選定了一個點,就不能選擇與其相鄰的節點。求選中點的最大權值和。
樹形
此題應該是 樹形 的一個升級版:基環外向樹。
什麼是基環外向樹森林
什麼是 基環外向樹?
基環外向樹,一般指一張 點數與邊數相等 的聯通圖,此時必然存在一個環,若把這個環當成一個節點,則原圖就形成了一棵樹。
什麼是 基環外向樹森林?
一張由若干個基環外向樹組成的圖(此時 點數仍然等於邊數),就是基環外向樹森林。
基環外向樹
那麼,基環外向樹 應該怎麼寫呢?
不難發現,對於某一棵基環外向樹,只要去掉環上的一條邊,它就成為一棵普通的樹了。
所以,我們就隨便去掉環上的一條邊。
然後分別以 這條邊連線的兩個端點 為根,跑 樹形。
這棵基環外向樹的貢獻就是兩次結果的較大值。
要注意的是,由於這兩個端點被一條邊連線了,因此這兩個端點不能同時選擇。
具體內容還是看程式碼吧。
程式碼
#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;
}