2021牛客多校#4 E-Tree Xor(区间异或,区间求交)

题意

给你n节点的树,每个节点的权值范围Li ≤ \leq Wi ≤ \leq Ri ,然后再给你n-1条边,边权定义为u,v节点权值的异或值。

现在问你,满足条件的W的取值有多少组。

思路

根据异或的性质,知二求一,那么对于这颗树,只要一个节点的权值确定,那么别的顶点的权值,跑一个dfs就可以求出来。应为

边权是已知的。

不妨让1号节点的权值为0,,那么可以预处理出别的节点的值W’i ,如果1号节点的取值为a,那么每个Wi=W’i ⨁ \bigoplus a。

由于题目对每个Wi是有限制,可以反推出来Li ≤ \leq W’i ⨁ \bigoplus a ≤ \leq Ri,进一步得出a的区间为[Li , Ri ] ⨁ \bigoplus W’i.

那么问题就转换为求n个区间[Li , Ri ] ⨁ \bigoplus W’i的交集。即是问题所求的答案。

问题1.如何求[Li , Ri ] ⨁ \bigoplus W’i

因为一个连续的区间异或完以后,不一定是一个连续区间,所以我们要先把区间划分成log(w)个连续的区间。

对于每个划分的区间,2 b是他们的区间长度,它们L二进制的最后b位一定为0,R二进制的最后b位一定为1,其余位相同。这样他们异或任何一个数,

区间都是连续的。并且新区间的左端点L是旧区间左端点L的除去最后b位异或上W的除去最后b位。由于区间长度是b,得新区间有右端点R=L+(1<<b)-1。

题目给定的区间是0 ≤ \leq Li ≤ \leq ≤ \leq Ri <230,因此可以用[0,230-1]的线段树来维护这个区间。

问题2.区间求交

对于n个区间求交,并且每个区间不一定是连续的。可以这样处理

对于每一段连续的区间,把他们起点和终点的下标放到一个PII里面,如果是起点,标记为-1,如果是终点标记为1,然后排序。

扫描一遍,维护一个dep,一开始为0,每次dep+=-it.second;如果dep为n,说明是n个区间的交集。加上这个区间的长度。

在这里插入图片描述

图片上是对俩个区间求交,方法是用上述过程。俩个结合起来看。

源代码

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
#define INF 0x7f7f7f7f
#define endl '\n'
#define mem(a, b) memset(a, b, sizeof(a))
#define open freopen("ii.txt", "r", stdin)
#define close freopen("oo.txt", "w", stdout)
#define IO                       \
	ios::sync_with_stdio(false); \
	cin.tie(0);                  \
	cout.tie(0)
#define pb push_back
typedef pair<ll, ll> PII;
const ll N = 1e5 + 10;
const double PI = 3.1415926535898;
const ll mod = 1e9 + 7;
using namespace std;
ll l[N],r[N];
ll w[N];
vector<PII>e[N];
vector<PII>p;
int n;
void dfs(int x,int fa)
{
	for(auto it:e[x])
	{
		if(it.first==fa)continue;
		w[it.first]=w[x]^it.second;
		dfs(it.first,x);
	}
}
void init()
{
	w[1]=0;
	dfs(1,0);
}
void findd(ll l,ll r,ll w,ll b)
{
	ll prew=w>>b;
	prew=prew<<b;
	ll prel=l>>b;
	prel=prel<<b;
	l=prew^prel;
	r=l+(1<<b)-1;
	p.pb(PII(l,-1));//记录端点的下标,起点标记为-1,终点标记为1,如果俩个下标相同的话,终点在后面,因为后面要排序,根据PII的优先级,分别标记为-1,和1,
	p.pb(PII(r,1));
	//cout<<l<<" "<<r<<endl;
}
void work(ll l,ll r,ll nl,ll nr,ll w,ll b)
{
	if(nl<=l&&r<=nr)
	{
		findd(l,r,w,b);
		return;
	}
	ll mid=(l+r)>>1;
	if(nl<=mid)work(l,mid,nl,nr,w,b-1);
	if(nr>mid)work(mid+1,r,nl,nr,w,b-1);
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>l[i]>>r[i];
	}
	for(int i=0;i<n-1;i++)
	{
		ll u,v,w;
		cin>>u>>v>>w;
		e[u].pb(PII(v,w));//vector存无向边
		e[v].pb(PII(u,w));
	}
	init();
	//for(int i=1;i<=n;i++)cout<<w[i]<<" ";
	for(int i=1;i<=n;i++)
	{
		work(0,(1<<30)-1,l[i],r[i],w[i],30);//划分每个区间,并且求出新区间。
	}
	sort(p.begin(),p.end());
	ll cnt=0;
	ll ans=0;
	for(int i=0;i<p.size();i++)//区间求交
	{
		cnt+=-p[i].second;
		if(cnt==n)
		{
			ans+=p[i+1].first-p[i].first+1;
		}
	}
	cout<<ans<<endl;
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值