[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;
}
posted @ 2021-06-26 13:36  oisdoaiu  阅读(38)  评论(0编辑  收藏  举报