AT_agc018_f [AGC018F] Two Trees 题解
先考虑怎么判断无解。对于一个点,它的儿子的子树和要么是 \(1\) 要么是 \(-1\),这两个值都为奇数,所以如果一个点在左树和右数的儿子数的奇偶性不同,那么肯定是无解的。
我们在两棵树的根上面连一条边,我们称之为根边。对于一个度数为奇数的点(根边也要参与度数统计),我们将它在两棵树上的点连一条边,我们称之为横边。这样就得到了一个度数全部为偶数的图。
我们在上面跑欧拉回路进行定向,如果一个横边的方向是从左到右,我们就把这个点(横边连接的两个点实际上是同一个点)权值设为 \(1\),反之我们就把这个点权值设为 \(-1\)。
下面是对于正确性的证明:
我们把欧拉回路拆成若干个环。
对于一个环,它要么经过两条横边,要么经过一条横边和根边。
考虑一个点 \(i\),对于经过的两条横边都在 \(i\) 的子树内的环,这种环对 \(i\) 的子树和的贡献恰好自己抵消了。
对于经过的一条横边在 \(i\) 的子树里的环,这种环对 \(i\) 的子树和的贡献是 \(1\) 或 \(-1\)。并且这种环一定有且只有一个,因为每一个这种环肯定会经过 \(i\) 和父亲这条边,而 \(i\) 和父亲这条边对于非根的点有且只有一个,对于根虽然没有父亲,但是我们也建立了根边。
这也就解释了为什么需要根边,它相当于对两棵树的根建了一个虚拟根,使得它们也有了父亲。如果没有根边,我们跑出来的方案中根的子树和就会变成 \(0\)。
这样我们就能够保证每个点的子树和一定为 \(1\) 或 \(-1\) 了。
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
int n,rt1,rt2,now[N],ans[N],idx;bool f=1,vis[N];
vector<pair<int,int> > e[N];
void dfs(int x){
while(!e[x].empty()){
auto v=e[x].back();e[x].pop_back();
if(vis[v.second])continue;
vis[v.second]=1;
if(((x==rt1&&v.first==rt2)||(x==rt2&&v.first==rt1))&&f) f=0;
else if(x<=n&&v.first>n) ans[x]++;
else if(x>n&&v.first<=n) ans[v.first]--;
dfs(v.first);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
int fa;cin>>fa;
if(fa<0){
rt1=i;
continue;
}
e[i].push_back({fa,++idx});
e[fa].push_back({i,idx});
}
for(int i=1;i<=n;i++){
int fa;cin>>fa;
if(fa<0){
rt2=i+n;
continue;
}
e[i+n].push_back({fa+n,++idx});
e[fa+n].push_back({i+n,idx});
}
e[rt1].push_back({rt2,++idx});
e[rt2].push_back({rt1,idx});
for(int i=1;i<=n;i++){
if((e[i].size()+e[i+n].size())&1){
cout<<"IMPOSSIBLE\n";
return 0;
}
if(e[i].size()&1){
e[i].push_back({i+n,++idx});
e[i+n].push_back({i,idx});
}
}
dfs(rt1);
cout<<"POSSIBLE\n";
for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
return 0;
}

浙公网安备 33010602011771号