[AGC018F] Two Trees

Two Trees

题解

首先,我们要说明一点,如果该题有解,那么我们用 { − 1 , 0 , 1 } \{-1,0,1\} {1,0,1}一定可以构造出一组解。
其实很容易理解的

对于一个点,它的权值有它儿子的个数决定,设子树 i i i的权值和为 v a l i val_{i} vali
容易发现 v a l u = ( 1 / − 1 ) − ∑ v ∈ S o n u v a l v val_{u}=(1/-1)-\sum_{v\in Son_{u}}val_{v} valu=(1/1)vSonuvalv
对于任意一个有合法子树点 v v v,如果我们将它子树中所有数的权值全部反转,它依旧是合法的,只不过 v a l v val_{v} valv会变为 − v a l v -val_{v} valv
于是我们一定存在一种方法使得有奇数个儿子的点的儿子的 v a l val val和为 0 0 0,有偶数个儿子的点的儿子的 v a l val val和为 1 / − 1 1/-1 1/1。这只需要通过调配儿子的 v a l val val 1 1 1 − 1 -1 1间转化就可以得到。
于是,点 u u u的权值可以只用 { − 1 , 0 , 1 } \{-1,0,1\} {1,0,1}就可以构造出一种合法解。

于是,我们只需要用 { − 1 , 0 , 1 } \{-1,0,1\} {1,0,1}去构造即可。

很容易发现,对于有偶数个儿子的节点,它的权值一定是 1 1 1 − 1 -1 1,称其为偶点;
对于有奇数个儿子的节点,它的权值一定为0,称其为奇点。
由于任意节点的子树的总权值和只能为 1 1 1 − 1 -1 1,所以我们需要将所有的偶点一一匹配,使得任意节点的子树中只有一个偶点没有被匹配。
对于匹配了的偶点,我们可以再它们之间连边。
由于每棵树只会对一个偶点产生 1 1 1的度数贡献,我们最后得到的一定是若干个偶环(由于一个节点在一棵树中只能匹配一个节点,不可能得到奇环)。
我们可以将这个偶环根据遍历到的顺序当作二分图,一边的节点赋值为 1 1 1,另一边赋值为 − 1 -1 1

只有当一个节点在两棵树上奇偶性不同时才会无解,否则一定有解。
时间复杂度 O ( n ) O\left(n\right) O(n)

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 100005
#define lowbit(x) (x&-x)
#define reg register
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int,int> pii;
const int INF=0x7f7f7f7f;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,Head[MAXN],Tot,val[MAXN];bool vis[MAXN];
struct edge{int to,nxt;}E[MAXN<<1];
void addedge(const int u,const int v){E[++Tot]=(edge){v,Head[u]};Head[u]=Tot;}
struct Tree{
	edge e[MAXN<<1];
	int siz[MAXN],head[MAXN],tot,p[MAXN],sta[MAXN],stak;
	void addEdge(const int u,const int v){e[++tot]=(edge){v,head[u]};head[u]=tot;}
	void dfs(const int x,const int fa){
		siz[x]=1;
		for(reg int i=head[x];i;i=e[i].nxt){
			const int v=e[i].to;if(v==fa)continue;
			dfs(v,x);siz[x]++;
		}
		stak=0;for(reg int i=head[x];i;i=e[i].nxt)if(e[i].to!=fa)sta[++stak]=p[e[i].to];
		for(reg int i=2;i<=stak;i+=2){
		const int u=sta[i-1],v=sta[i];
			addedge(u,v);addedge(v,u);
		}
		p[x]=(siz[x]&1)?x:p[x]=sta[stak];
	}
	void build(){
		int root;
		for(reg int i=1,x;i<=n;++i){
			read(x);if(x<0){root=i;continue;}
			addEdge(i,x);addEdge(x,i);
		}
		dfs(root,0);
	}
}T1,T2;
void dfs(const int x,const int w){
	val[x]=w;vis[x]=1;
	for(reg int i=Head[x];i;i=E[i].nxt)
		if(!vis[E[i].to])dfs(E[i].to,-w);
}
signed main(){
	read(n);T1.build();T2.build();
	for(reg int i=1;i<=n;++i)if((T1.siz[i]&1)!=(T2.siz[i]&1)){puts("IMPOSSIBLE");return 0;}
	for(reg int i=1;i<=n;++i)if(!vis[i]&&(T1.siz[i]&1))dfs(i,1);
	puts("POSSIBLE");for(reg int i=1;i<=n;++i)printf("%d ",val[i]);puts("");
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值