妙妙子树上好题 XOR, Tree, and Queries

XOR, Tree, and Queries

大意:
给定一棵树n个节点,然后告诉你m个点对之间的简单路径异或和。要求构造出满足条件的一棵树,使得所有边权满足条件,且所有边权的异或和尽可能小

思路:

实在妙妙子,我看到构造所有边权就是直接傻眼,寻思线性基还是trie好像都不能给出构造方案。没想到题目只用了异或的最简单的性质,加上反常的建图,就解决了...

首先树上异或的话,一个常见的trick就是做根到节点的前缀异或和。我们记pi表示根节点到i的路径上的边权异或和,那么不难发现,对于任意两个节点u,v,它们之间的简单路径异或和可以按如下方式转化

 等于u-lca的路径与lca-v的异或和,也就是\large p_u\oplus p_{lca}\oplus p_v\oplus p_{lca}=p_u \oplus p_v

如果我们已知u到v的路径异或和是x,则\large p_u \oplus p_v=x\Rightarrow p_u=p_v\oplus x

所以我们可以通过上述结论来尝试推出对应节点的值。

所以,我们可以按给的边权异或和关系来重新建图,这样会得到一个个连通块。每一个连通块内,我们从任意一个起点出发(不妨假设它的pi值为0,这当然是合理的,因为想要修改它为x的话,我们只要让连通块内的点的p值都异或上x就可以了。个人感觉这里并不好想,因为完全是在两个不同的图上面考虑,这里的修改权值意味着在树上一大段路径的修改。但是我们在推的时候肯定是保证了可行性,而我们不用找到具体的构造方案,只要它一定存在就可以了),可以推出其余点的p值,然后,如果里面有环,并且发现某一个点从不同路径走有不同值时,意味着该情况无解,这题就结束了。否则,我们就能得到一组满足条件的边权。

然后考虑题目的另一个约束条件:边权的异或和最小。

套用上述结论,对于相邻节点u,v,\large e_{u,v}=p_u \oplus p_v,所以我们可以将所求式子改写

e_1 \oplus e_2 ... \oplus e_{n-1}=(p_{u_{1}} \oplus p_{v_{1}}) \oplus (p_{u_{2}} \oplus p_{v_{2}})...(p_{u_{n-1}} \oplus p_{v_{n-1}})

显然每一个pi有贡献当且仅当它出现次数为奇数,也就是i节点的度数为1

所以我们把所有度数为1的节点找出来,它们的异或和,就是我们的答案。

然后要优化的话,我们一定是在每一个连通块里统一异或上一个值。如果某一个连通块的大小是偶数,意味着我们要修改的值x会异或上偶数次,相当于没有操作。所以大小为偶数的连通块堆我们没帮助。

而一旦出现大小为奇数的连通块,也就是最后结果会异或上我们的修改值x。我们只要让x等于当前答案,那么最后的总体异或值就是0了。此时我们只要在该连通块内修改每一个点的p值,就能退出得到所有边权了。

所以,看起来是一道很伤脑筋的构造题,其实最后的结果只有三种情况:无解,0,一个常值。

至于前面的如何在一个连通块内推出所有值,dfs一下即可,有点像染色。我也看到过有人是拆开按位考虑,然后每一位就变成了0或者1的情况,那样就是二分图染色来标记了。

直接做的话,我们的复杂度可以来到喜人的O(n)

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=3e5+10;
struct ty
{
	ll t,l,next;
}edge[N<<1];
ll cn=0;
ll head[N];
void add(ll a,ll b,ll c)
{
	edge[++cn].t=b;
	edge[cn].l=c;
	edge[cn].next=head[a];
	head[a]=cn;
}
ll e1[N],e2[N];
ll fl[N];//度数为奇数的话就标记 
ll n,m;
ll vis[N];
ll p[N];//答案 
ll siz[N];
void dfs(ll id,ll k)
{
	vis[id]=k;
	for(int i=head[id];i!=-1;i=edge[i].next)
	{
		ll y=edge[i].t;
		if(vis[y]==0)
		{
			p[y]=p[id]^edge[i].l;
			dfs(y,k);
		}
		else if(p[y]!=(p[id]^edge[i].l))
		{
			cout<<"No"<<endl;
			exit(0); 
		}
	}
}
void solve()
{
	memset(head,-1,sizeof head);
	cin>>n>>m;
	for(int i=1;i<n;++i)
	{
		cin>>e1[i]>>e2[i];
		fl[e1[i]]^=1;fl[e2[i]]^=1;
	}
	for(int i=1;i<=m;++i)
	{
		ll a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
		add(b,a,c);
	}
	for(int i=1;i<=n;++i)
	{
		if(vis[i]) continue;
		dfs(i,i); 
	}
	ll ans=0;
	for(int i=1;i<=n;++i)
	{
		if(fl[i]) ans^=p[i],siz[vis[i]]++;
	}
	for(int i=1;i<=n;++i)
	{
		if(siz[i]%2==0) continue;
		//cout<<"df "<<i<<endl;
		for(int j=1;j<=n;++j)
		{
			if(vis[j]==i) p[j]^=ans;
		}
		break;
	}
	cout<<"Yes"<<endl;
	for(int i=1;i<n;++i)
	{
		ll s=p[e1[i]] ^ p[e2[i]];
		cout<<s<<" ";
	}//cout<<p[e1[i]] ^ p[e2[i]]<<' ';
	cout<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	solve();
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值