1. 程式人生 > >[agc018f]Two Trees——神仙構造題+歐拉回路or黑白染色 dalaos' blogs Some Links

[agc018f]Two Trees——神仙構造題+歐拉回路or黑白染色 dalaos' blogs Some Links

題目大意:

給定兩顆帶了標號的有根樹,大小都是n,現在讓你對每一個點賦一個權值,使得每一個點的子樹和為-1或1。

思路:

首先我們可以算出每一個點的權值的奇偶性,一個點如果在兩顆樹中奇偶性不一樣一定無解,反之一定有解。
考慮怎麼構造,對於一個偶點,我們直接賦為0,對於奇點我們賦上-1或1。
它的核心思想是利用歐拉回路的性質使得子樹中的點儘量1和-1配對。
建邊方式如下:

  1. 兩棵樹按照原來的邊分別建立。
  2. 如果點u為奇點,即有偶數顆子樹,在兩棵樹的對應點上面連邊。
  3. 兩棵樹的根之間連一條邊。

不難發現這個新圖的度數都為偶數,於是對於這個新圖求歐拉回路,對於奇點,如果它的附加邊是從A->B,那麼我們賦權值為1,反之為-1。
下面證明演算法的正確性:
對於每一個奇點,我們在歐拉回路中計算它的權值當且經當這條路徑通過這個點跨越了一棵樹。

  1. 對於一個點u,一條從u出發並且又回到u的路徑上的點集的點權和必定為0.(必定是走出去一次,走回來一次)
  2. 對於一個點u,一條從u的子樹出發並且從u的子樹回來的路徑,其中經過的u的子樹中的點集的權值和為0.(即使走出了子樹,那必定是走到另外一棵樹上,那必定是走出去一次,走回來一次)
  3. 對於一個點u,一條從u的子樹出發並且不從u的子樹回來的路徑,子樹中的權值和必定是1或-1.
  4. 於是對於一個偶點,它將有若干條2路徑和1條3路徑,自身權值為0,所以子樹和為1或-1.
  5. 對於一個奇點,它的子樹和(不包括它本身)可能為0(若干條起點終點都在子樹內和一條起點終點都在子樹外)或者+2,-2(若干條起點終點都在子樹內和兩條3路徑),加入它本身之後,權值和一定為+1或者-1。

於是證明完畢。
這道題還有更簡單的黑白染色方法,方法類似,這裡不再贅述。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
typedef long long ll;

using namespace std;

void File(){
	freopen("agc018f.in","r",stdin);
	freopen("agc018f.out","w",stdout);
}

template<typename T>void read
(T &_){ T __=0,mul=1; char ch=getchar(); while(!isdigit(ch)){ if(ch=='-')mul=-1; ch=getchar(); } while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar(); _=__*mul; } const int maxn=3e5+10; int n,f[maxn],g[maxn],sf[maxn],sg[maxn],rf,rg; int beg[maxn],las[maxn<<1],to[maxn<<1],cnte=1; int euler[maxn],cnt,ans[maxn]; bool vis[maxn<<1]; void add_edge(int u,int v){ las[++cnte]=beg[u]; beg[u]=cnte; to[cnte]=v; las[++cnte]=beg[v]; beg[v]=cnte; to[cnte]=u; } int s[maxn],tp; void dfs(int u){ s[++tp]=u; for(int &i=beg[u];i;i=las[i]){ if(vis[i/2])continue; vis[i/2]=1; dfs(to[i]); } euler[++cnt]=u; } int main(){ File(); read(n); REP(i,1,n){ read(f[i]); if(f[i]>0){ ++sf[f[i]]; add_edge(f[i],i); } else rf=i; } REP(i,1,n){ read(g[i]); g[i]+=n; if(g[i]>n){ ++sg[g[i]]; add_edge(g[i],i+n); } else rg=i+n; } REP(i,1,n)if(sf[i]%2!=sg[i+n]%2) return puts("IMPOSSIBLE"),0; add_edge(rf,rg); REP(i,1,n)if(sf[i]%2==0) add_edge(i,i+n); dfs(1); //REP(i,1,cnt)cout<<euler[i]<<endl; //cout<<endl; REP(i,1,cnt-1)if(abs(euler[i]-euler[i+1])==n){ int id=min(euler[i],euler[i+1]); if(sf[id]%2==1)continue; if(euler[i]<=n)ans[id]=1; else ans[id]=-1; } puts("POSSIBLE"); REP(i,1,n)printf("%d ",ans[i]); return 0; }