[基環樹入門][ZJOI2008]騎士/CF1027F Session in BSU

最近一下子看到好多基環樹的題,先做兩道入門吧

解釋基環樹——樹加一條邊使之成環

這裏想說一下基環樹和tarjan最基本的區別:基環樹入度出度至多爲1。

樹形DP還有一種特殊情況——基環樹的DP,基環樹就是一顆樹上再加一條邊,處理基環樹問題時一般先找出環再處理,然後再搞來搞去就行啦

例如[ZJOI2008]騎士

利用基環樹只有一條多餘邊的性質對其進行特判,其餘樹形dp  

ps  f[i][0] f[i][1]分別代表選這個點和不選這個點的情況、這題注意卡常優化

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0' || ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0' && ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}
const int N=3111111;
long long tot,n,x,s3,a[1100000],mx,ans,to[N],k,s,s1,s2,r,dp[1100000][2],vis[N],head[N],next1[N];
inline long long Max(const long long &a,const long long &b){return a<b?b:a;}
void add(int x,int y)
{  next1[++tot]=head[x];
   head[x]=tot;
   to[tot]=y;
}
inline void dfs(int x,int fa)
{
    for (register int i=head[x];i;i=next1[i])
    { long long  v=to[i];
       if (v==fa)  continue;
       if (vis[v])  {k=1;s=i;s1=x;s2=v;}  else {vis[v]=1; dfs(v,x);}
    }
}
inline void tdp(const int &x,const int &fa)
{
    dp[x][1]=a[x]; dp[x][0]=0;
    for (register int i=head[x];i;i=next1[i])
    {  long long v=to[i];
       if (v==fa)  continue;
       if (s%2)  s3=s+1;else s3=s-1;
       if (i!=s && i!=s3)  {//	cout<<x<<' '<<v<<' '<<i<<' '<<s<<endl;  
       tdp(v,x); dp[x][1]=Max(dp[x][1],dp[x][1]+dp[v][0]);dp[x][0]=Max(dp[x][0],dp[x][0]+Max(dp[v][0],dp[v][1]));}
    }
    if (k==1 && r==x)  dp[x][1]=-1e12;
}
int main()
{
  n=read();
  for (register int i=1;i<=n;i++)
  {  a[i]=read(); x=read();  add(i,x); add(x,i); }
  for (register int i=1;i<=n;i++)
  if (!vis[i]) 
  { k=0;s=0;mx=0;
    vis[i]=1; dfs(i,0);
    if (k==1) { // cout<<s1<<' '<<s2<<endl;  
                r=s1;tdp(i,0);mx=Max(mx,Max(dp[i][0],dp[i][1])); 
                r=s2;tdp(i,0);mx=Max(mx,Max(dp[i][0],dp[i][1])); }  
               else {tdp(i,0);mx=Max(mx,Max(dp[i][0],dp[i][1]));} 
    ans+=mx;
  }
  cout<<ans<<endl;
} 

 CF1027F Session in BSU

題意:

有n個人,每個人有兩個時間點可以參加考試,任意兩人不能在同一時間點參加考試。問最遲考試的人的考試時間最早是多少?

n≤106.n≤106.

一般遇到給定兩個人的關係,或者兩個條件、又或者牌的反轉都可以考慮一下基環樹

題解:

離散化,將每人的兩個時間點連邊。題意可以轉化爲:給每條邊定向滿足任意一點入度至多爲1,使得每條邊指向的點的編號最大值最小。

要滿足任意一點入度至多爲1,原圖必須是基環森林(每個連通塊必須是基環樹(注意可以有多個基環樹,同時判基環樹時邊不能重複走,需打標記(寫掛的原因))或樹)。先判不合法的情況。然後對於每個連通塊,如果是基環樹,那麼每個點一定有1的入度,用最大值更新答案;如果是樹,那麼最多使得根節點沒有入度,其餘點都有1的入度,所以把最大編號設爲根,用次大值更新答案。每個連通塊的值的最大值即爲最終答案。

複雜度O(nlogn).

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0' || ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0' && ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}
const int N=3111111;
vector <int> v;
long long k,k1,tot=1,n,x,e,mx2,ss,s3,p[N],a[1100000],zhi[N],b[N],mx,ans,to[N],s,s1,s2,r,vis[N],head[N],next1[N];
inline long long Max(const long long &a,const long long &b){return a<b?b:a;}
void add(int x,int y)
{  next1[++tot]=head[x];
   head[x]=tot;
   to[tot]=y;
}
inline void dfs(int x,int fa)
{  if (zhi[x]<=mx && zhi[x]>mx2) mx2=zhi[x]; 
  if (zhi[x]>mx)  mx2=mx, mx=zhi[x];
	for (register int i=head[x];i;i=next1[i])
	{ long long  v=to[i];
	   if (v==fa)continue;
	   if (p[i]) continue;//走過的邊不會再走
	   if (vis[v]){k=1;p[i]=1; s1=i^1; p[s1]=1;e++;}  
	   else {p[i]=1; s1=i^1;p[s1]=1;vis[v]=1; dfs(v,x);}
	}
}
int main()
{
  n=read();
  for (register int i=1;i<=n;i++)
  {  a[i]=read(); b[i]=read(); 
     v.push_back(a[i]); v.push_back(b[i]);   }
     sort(v.begin(),v.end());
     ss=unique(v.begin(),v.end())-v.begin();
     for (int i=1;i<=n;i++)
  {  k=lower_bound(v.begin(),v.begin()+ss-1,a[i])-v.begin()+1;  zhi[k]=a[i];
     k1=lower_bound(v.begin(),v.begin()+ss-1,b[i])-v.begin()+1;  zhi[k1]=b[i];
     add(k,k1); add(k1,k);} 
  for (register int i=1;i<=ss;i++)
  if (!vis[i]) 
  { e=0;//有多個基環樹是可以的
    k=0;s=0;mx=0;mx2=0;
    vis[i]=1;
	dfs(i,0);
	if (e>1)  {break;}
    if (k==1) { ans=max(mx,ans); }  
	else {ans=max(mx2,ans);}
  } 
   if (e>1)  cout<<"-1"<<endl;
        else  cout<<ans<<endl;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章