2021牛客暑期多校训练营4 E Tree Xor

2021牛客暑期多校训练营4 E - Tree Xor

题目描述
Bob has a tree with n n n nodes and the weight of i t h i^{th} ith node is w i w_i wi
But Bob forgot w 1...... n w_{1......n} w1......n,he only remembers w i w_i wi is an integer in [ l i , r i ] [l_i,r_i] [li,ri] and w u   x o r   w v w_u~xor~w_v wu xor wv for each edge ( u , v ) (u,v) (u,v) in the tree.
Now Bob wants to know the number of possible values of w 1... n w_{1...n} w1...n
XOR means bitwise exclusive OR

输入描述
The first line has one integers nn.

Then there are nn lines, the i-th line has two integers l i , r i l_i,r_i li,ri

Then there are n-1n−1 lines, each line has three integers
u , v , w u   x o r   w v u,v,w_u~xor~w_v u,v,wu xor wvdenote the infomation for each edge.

1 ≤ n ≤ 1 0 5 1\leq n \leq 10^5 1n105 0 ≤ l i ≤ r i < 2 30 0\leq l_i\leq r_i<2^{30} 0liri<230 0 ≤ w u   x o r   w v < 2 30 0\leq w_u~xor~w_v<2^{30} 0wu xor wv<230

输出描述
Output the answer.

题意
一颗有点权的数,已知所有点权值的范围,已知任意一条边两点权值的异或值,求树有多少种可能性(只要有一个点权不同就是一种方案)
分析
先假设一个点为树根,把它赋值为a,这样我们就能通过边求出所有点的权值。所以树的所有可能即为a的所有可能。
题解
首先,我们假设 a = 0 a=0 a=0,然后dfs搜索整棵树,这样整棵树就都有了一个初始值,可以把它成为基础解系。当a为其他值的时候,我们只要在每个点的基础解上面异或上a就可以得到这个点的值,然后再考虑这个点是不是在这个范围内就可以了。

显然,这种暴力思维过于暴力,不仅枚举a就超时了,而且还要每次都dfs搜索整棵树的所有节点是不是符合节点的范围要求。

那么我们在得到基础解系之后换一种想法,因为异或的逆运算还是异或,所以我们只要用基础解去异或需要的区间里的所有值,那么就会得到,对于这个节点,所有允许的a值。我们得到所有点的允许的a值,取交,就可以得到所有可能的a,也就是答案了。

但是很明显我们还需要一个算法来得到一个点异或一个区间的值,显然直接异或区间的左右边界是不对的。

这时我们假设建立了一颗线段树,这颗线段树的根节点是 0 ∼ 2 n − 1 0 \sim 2^n-1 02n1,这颗线段树有这样的性质:假设树根的深度为n,往下深度递减,即树根的两个子节点深度为n-1,对于所有的节点,深度为dp,它的l,r内,二进制的后dp位是 0 , 0 , ⋯   , 0 ⏟ d p \underbrace {0,0,\cdots,0}_{dp} dp 0,0,,0 ~ 1 , 1 , ⋯   , 1 ⏟ d p \underbrace {1,1,\cdots,1}_{dp} dp 1,1,,1。这样的性质为我们的“区间异或”创造了条件。如果我们想让一个区间都异或某一个值,就可以用这样的线段树表示。

这样的线段的好处就是,如果你用一个值 n n n 来异或具有以上性质的区间,假设n的二进制位数为 l e n n lenn lenn ,当 d p ≥ l e n n dp \ge lenn dplenn 时,那么就相当于拿 l e n n lenn lenn 位的所有二进制组合来异或这个值,那么得到的答案一定是 l e n n lenn lenn 位是所有二进制组合,也就是 0 , 0 , ⋯   , 0 ⏟ l e n n \underbrace {0,0,\cdots,0}_{lenn} lenn 0,0,,0 ~ 1 , 1 , ⋯   , 1 ⏟ l e n n \underbrace {1,1,\cdots,1}_{lenn} lenn 1,1,,1 。区间中大于lenn的二进制位保持不变,所以得到的结果就还是这个区间;当 d p ≤ l e n n dp \le lenn dplenn 时,后dp位同上,得到的是后dp位的所有组合, d p ∼ l e n n dp\sim lenn dplenn位为 n n n和这个区间的 d p ∼ l e n n dp\sim lenn dplenn 位的异或值。这样我们用线段树的方式划分所有点的区间,并用这个线段树的性质求出了所有点异或区间得到的区间,我们的下一个问题就是如何求这n组区间的交?

这时候我们借鉴扫描线的思想,把所有区间的左端,带权值为 1 加入到一个vector<pair<int,int> >中,把区间的右端,带权值为-1也加入进去。以第一变量为关键字sort一下,再逐个扫,一个变量记录扫进来的权值和,当权值和达到n的时候,就是当前线段进入到了n个区间的交,那么下一个点必然为权值为-1的点,因为n个区间全交在这里了,这时答案加上下一个点到这一个点的距离+1,这样我们就得到了n个区间的交。这样我们就完成了这道题。

可能有人会发现,在求点异或区间的时候,这颗线段树建立不了,因为建立一颗线段树,至少需要 2 ∗ n − 1 2*n-1 2n1的空间,然而为了保证所有区间都在范围内,我们需要把根节点的区间定为 0 ∼ 2 30 − 1 0\sim 2^{30}-1 02301 显然需要的空间达到了 1 e 9 1e9 1e9,肯定开不了这么大的tree数组,那么怎么办呢?其实我们并不需要线段树来维护sum和lazytag,只是借鉴了线段树的构造形式,或者说,我们用到的其实不是线段树,就是一个普通的二叉树,所以并不用真的建树,按照二叉树的规则直接进行搜索就行。这样我们就可以在时间和空间复杂度都合理的情况下解决这道题啦!复杂度 O ( N l o g 2 N ) O(N log_2N) O(Nlog2N)

#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define ms(x, n) memset(x,n,sizeof(x));
#define fi first;
#define se second;
#define pb push_back;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const double eps=1e-8;
const int maxn=1e5+10;
const int mod=571373;
const ll inf=0x7f7f7f7f7f7f7f7f;
const double pi=acos(-1.0);
#define CC getchar()
template <typename T>
inline void read(T &s){
     T t=1; char k=CC; s=0;
    for (;k<'0'||k>'9';k=CC) if (k=='-') t=-1;
    for (;k>='0'&&k<='9';k=CC) s=(s<<1)+(s<<3)+(k^48);
    s*=t;
}

int cnted=0,head[maxn],vis[maxn];

vector<pii> ans,smx;

struct point{
	int l,r,val;
}p[maxn];

struct edge{
	int val,to,nxt;
}edg[maxn<<1];

void dfs(int x)
{
	for(int i=head[x];i!=-1;i=edg[i].nxt)
	{
		if(vis[edg[i].to])continue;
		vis[edg[i].to]=1;
		p[edg[i].to].val=p[x].val^edg[i].val;
		dfs(edg[i].to);
	}
}

void add(int nl,int nr,int l,int r,int val,int lenv,int dp)
{
	if(nl>=l&&nr<=r)
	{
		if(lenv<=dp) ans.push_back({nl,nr});
		else{
			int sta=((nl^val)>>dp)<<dp;
			ans.push_back({sta,sta+(1<<dp)-1});
		}
		return ;
	}
	if((nr+nl)>>1>=l) add(nl,(nr+nl)>>1,l,r,val,lenv,dp-1);
	if(((nr+nl)>>1)+1<=r) add(((nr+nl)>>1)+1,nr,l,r,val,lenv,dp-1);
}
int main() {
    IOS;
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
    	head[i]=-1;
    	cin>>p[i].l>>p[i].r;
    }
    int u,v,val;
    for(int i=1;i<n;i++)
    {
    	cin>>u>>v>>val;
    	edg[++cnted]=(edge){val,v,head[u]};head[u]=cnted;
    	edg[++cnted]=(edge){val,u,head[v]};head[v]=cnted;
    }
    vis[1]=1,p[1].val=0;
    dfs(1);
    for(int i=1;i<=n;i++)
    {
    	int lenv=log(p[i].val)/log(2);
    	if(p[i].val==0) lenv=-1;
    	add(0,(1<<30)-1,p[i].l,p[i].r,p[i].val,lenv+1,30);
    }
    for(vector<pii>::iterator it=ans.begin();it!=ans.end();it++)
    	smx.push_back({it->first,1}),smx.push_back({it->second+1,-1});
    sort(smx.begin(),smx.end());
    int sum=0,ret=0;
    for(int i=0;i<(int)smx.size();i++)
    {
    	sum+=smx[i].se;
    	if(sum==n) ret+=smx[i+1].first-smx[i].first;
    }
    cout<<ret<<endl;
    return 0;
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值