[AGC018F] Two Trees
tag:构造,欧拉回路
题意
给定两个点数均为 \(n\) 的有根树,给标号赋值使得两棵树上任意一个子树和均为 \(1\) 或 \(-1\)
一棵树
首先只考虑一棵树,显然可以直接从叶子往上构造。但是为了方便推广到两棵树,考虑找出一个等价过程。
由于一个子树对父亲的 \(sz\) 的贡献只有 \(1,-1\),所以考虑给树边定向。儿子指向父亲代表贡献为 \(1\),反过来代表贡献为 \(-1\)。
由于整个树的权值和也要为 \(1\) 或 \(-1\),所以可以新建一个根 \(0\),然后 \(0\) 连向当前根,对这条边的定向就对应着整棵树的贡献。
所以先给所有边定向(可以随机定)。对于一条儿子指向父亲的边,就把儿子 \(+1\),父亲 \(-1\),这样子树大小改变的只有以儿子为根的一个子树,反过来同理。
实际上就是定向完后,\(a_i=\) 出度-入度。
两棵树
到了两棵树的时候,问题来了,如何保证对两棵树定向后的 \(a_i=b_i\) 呢?直接构造的话实际上是不好考虑的,我们可以再加一些边。
首先如果两棵树上某个标号对应的两个点,度数的奇偶性不一样,那么一定无解。
所以某个标号对应的两个点,度数的奇偶性相同;如果度数为奇数,就把这两个点连起来。
这样得到的新图满足,连通(两个 \(0\) 节点之间一定有连边),而且所有点度数均为偶数,也就是说存在欧拉回路。而根据欧拉回路的性质,每个点的入度都等于出度。于是如果按照欧拉回路去定向,每个点的点权就都为 \(0\),自然就满足相等了。
#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void Read(T &n){
char ch; bool flag=false;
while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
if(flag)n=-n;
}
enum{
MAXN = 100005
};
int n;
struct road{int u, v;}e[MAXN*3];
int ecnt;
vector<int>to[MAXN<<1];
int d[MAXN<<1];
inline void Add_Edge(int u, int v){
e[++ecnt] = (road){u,v};
to[u].push_back(ecnt);
to[v].push_back(ecnt);
d[u]++; d[v]++;
}
char vis[MAXN*3];
int ans[MAXN];
void dfs(int x){
while(!to[x].empty()){
int u = to[x].back();
to[x].pop_back();
if(vis[u]) continue;
vis[u] = true;
int v = x==e[u].v?e[u].u:e[u].v;
if(u>2*n)
if(x>v) ans[v] = 1;
else ans[x] = -1;
dfs(v);
}
}
int main(){
Read(n);
for(register int i=1; i<=n; i++){
int fa; Read(fa);
if(fa==-1) Add_Edge(n+1,i);
else Add_Edge(fa,i);
}
for(register int i=1; i<=n; i++){
int fa; Read(fa);
if(fa==-1) Add_Edge(n+1+n+1,i+n+1);
else Add_Edge(fa+n+1,i+n+1);
}
for(register int i=1; i<=n+1; i++){
if((d[i]^d[i+n+1])&1) return puts("IMPOSSIBLE"), 0;
if(d[i]&1) Add_Edge(i,i+n+1);
}
dfs(1);
puts("POSSIBLE");
for(register int i=1; i<=n; i++) printf("%d ",ans[i]);puts("");
return 0;
}