• E-Tree Xor_2021牛客暑期多校训练营4


    E-Tree Xor_2021牛客暑期多校训练营4 (nowcoder.com)

    题目:

    题意:

    给n个点,n-1条边。每个点都有一个取值范围([l_i,r_i]),每条边 x->y 都有一个边权w,表示 x 点到 y 点时点值需异或上w,求最多有多少个数fx可以选择,使得fx从任意一点出发,异或上相邻节点边权都符合相邻节点取值范围。

    思路:

    首先,最暴力的想法就是将1设为根节点,遍历相邻节点,把相邻节点区间每一个数(x_i)异或上边权w,得到一堆离散的点,把这些点存起来递归子树,执行相同操作,又会得到一堆离散的点。递归结束后,统计一下每个点出现的次数,次数=n说明这个点可行,答案加1。(10^9)的数据量显然复杂度爆炸。

    现在考虑优化,首先我们还是需要dfs一遍求出每个点需要异或的值。

    问题来了,区间异或一个值会得到一堆散点,如何处理这堆散点?

    例 求([1,4]igoplus2)的值, 我们会得到 [0,1,3,6],看似散了,其实不然。我们放到01字典树上看看。

    我们将2(010)从高位开始与字典树异或。

    我们会观察到01字典树进行异或运算的一个性质:若当前二进制位为0,左右子树不变,若为1,则左右子树互换。

    例如:我们找1(001)的时候,与2(010)进行异或运算。(看2的二进制位是否为1

    ​ 第一次 (0igoplus0=0) 不变化

    ​ 第二次 (0igoplus1=1) 左子树变成右子树

    ​ 第三次 (1igoplus0=1) 不变化

    ​ 按照变换关系放到图中刚好就是3的位置 也就是 011.

    有了这个性质之后,我们会发现一个事实,若某个节点的子树节点完全属于([l_i,r_i]),那么这个节点一下的子树就没有必要进行异或操作了,因为异或操作只会变化左右子树,而这个节点的子树再怎么变换也都是这个结点的子树。

    例:我们处理2,3时,处理到2,3共同的祖先就可以了,此时我们会得到(00?),?取值0,1得到000=0,001=1 。

    有了这两个结论,我们就可以处理一些复杂的。

    例: 求([0,4]igoplus 5)

    ​ 5(101)

    ​ 我们先对左半边异或,(0igoplus1=1) ,左子树变右子树。

    ​ 继续向下异或时发现区间包含[0,3],则不需要继续向下异或了,因为继续异或也都是此节点的子树。我们这是会得到 (1??) ?自由取 0,1 会得到一个区间[4,7]。

    ​ 再对右半部分异或,最终会得到 (4igoplus5=5).

    ​ 处理结束后我们会得到两个区间,这两个区间内部是连续的。

    那么这时我们处理一段区间的时候,按照线段树的思想划分区间,若一个节点的范围完全属于区间范围,这时我们只需要将这个节点以上的高位二进制与w进行异或,节点一下的二进制位全变成0,处理完之后会得到一段区间的左端点,右端点根据节点的深度和左端点值可以记录出来。

    再来看题,我们可以把一个([l_i,r_i])区间分成若干个内部连续且不相交的区间。处理完n个之后,若一个区间出现了n次,那么答案就加上这段区间的长度。具体实现看代码注释。

    #include<bits/stdc++.h>
    #define mk make_pair
    #define PII pair<int,int> 
    #define pb push_back
    using namespace std;
    int read(){
    	int x=0,f=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9'){
    		if(ch=='-') f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9'){
    		x=(x<<1)+(x<<3)+(ch^48);
    		ch=getchar();
    	}
    	return x*f;
    }
    const int qs=1e5+7;
    int n,w[qs],l[qs],r[qs];
    struct node{
    	int nx,val;
    	node(){}
    	node(int nx,int val):nx(nx),val(val){};
    };
    vector<node> v[qs];
    map< int ,int> mp;
    map< int ,int>::iterator it;
    void dfs(int x,int fa){
    	for(int i=0;i<v[x].size();++i){
    		int fx=v[x][i].nx,fv=v[x][i].val;
    		if(fx==fa) continue;
    		w[fx]=w[x]^fv;
    		dfs(fx,x);
    	}
    }
    
    void updata(int L,int R,int l,int r,int val,int pos){
    	if(l<=L&&r>=R){
    		//区间完全包含 
    		//fl就是处理完后新的区间左端点 
    		int fl=(L^val)&(((1<<30)-1)^((1<<pos)-1));
    		// fr=fl+区间数量 
    		int fr=fl+(1<<pos)-1;
    		mp[fl]++;	//记录新的区间左右端点 
    		mp[fr+1]--;
    		return;
    	}
    	int mid=(L+R)/2;
    	//递归找区间  线段数思想 
    	if(l<=mid) updata(L,mid,l,r,val,pos-1);
    	if(r>mid) updata(mid+1,R,l,r,val,pos-1);
    }
    
    void work(){
    	int ans=0;
    	int cnt=0,flag=0;
    	for(it=mp.begin();it!=mp.end();++it){
    		int fx=it->first; int fy=it->second;
    		cnt+=fy;
    		if(flag) ans+=fx-flag,flag=0;
    		//若左端点出现了n次,说明这段区间一定可行 
    		if(cnt>=n) flag=fx;
    	}
    	printf("%d
    ",ans);
    }
    
    int main(){
    	n=read();
    	for(int i=1;i<=n;++i) l[i]=read(),r[i]=read();
    	for(int i=1;i<n;++i){
    		int x,y,z;
    		x=read(),y=read(),z=read();
    		v[x].pb(node(y,z));
    		v[y].pb(node(x,z));
    	}
    	w[1]=0; //将1的异或值设为0 
    	dfs(1,0);	//递归得到各点异或值 
    	mp[l[1]]++;	//记录左右端点 
    	mp[r[1]+1]--;
    	for(int i=2;i<=n;++i){	//处理各个区间 
    		updata(0,(1<<30)-1,l[i],r[i],w[i],30);
    	}
    	work();
    	return 0;
    }
    

    蜜汁操作,同样的代码概率超时,真就随机ac呗,最快905ms。

  • 相关阅读:
    唐寅 《桃花庵歌》
    asp.net 后台隐藏div
    dataset的用法
    C#中的DateTime类型加减
    discuz! x2.5 文章添加分享按钮
    asp.net学习小网站
    table固定行和表头
    aspx.net开源的画图组件
    Global.asax详解
    int.Parse() int.TryParse
  • 原文地址:https://www.cnblogs.com/Suki-Sugar/p/15068567.html
Copyright © 2020-2023  润新知