• UOJ 435


    话说这货不去切eJOI在这干啥子呢(

    UOJ题目页面传送门

    有一棵大小为(n)的树,根为(1),节点(i)有一个权值(a_i)。支持(3)(q)次操作:

    1. ( exttt1 x y v):令所有在路径(x o y)上的点的权值增加(v),保证(v=pm1)
    2. ( exttt2 x y):求路径(x o y)上权值(>0)的点数;
    3. ( exttt3 x):求子树(x)内权值(>0)的点数。

    强制在线。

    (n,qinleft[1,10^5 ight],a_iinleft[-10^9,10^9 ight])

    一眼重剖。于是转化为线性结构上的区间修改和区间查询。然后就不会了

    看到区间查询排名,想到线段树套平衡树,但是这是区间修改,歇的了。

    于是,分块是无所不能的(

    注意到一个性质,由于(v=pm1),所以若(a_ileq-q)(a_igeq q+1),则([a_i>0])永远无法改变。所以不妨等效地将(<-q)的赋成(-q),将(>q+1)的赋成(q+1),此时值域(mathrm O(q))

    考虑将线性结构(a)分成(sz1)块,每块(i)内维护后缀计数(cnT_i)(cnT_{i,j})表示块(i)(geq j)的数的个数。再维护一个整体增加标记(add_i),表示该块被整体增加过多少。

    • 区间修改:对于两边不满的块,暴力修改,每个数(pm1)的话(cnT)是可以(mathrm O(1))更新的(当然你重构我也不拦你)。对于中间的整块们,直接修改它们的整体增加标记。(mathrm O!left(sz1+dfrac n{sz1} ight))

    • 区间查询:对于两边不满的块,暴力计数,注意每个数真正的值是它在(a)数组中的值加上所在块的整体增加标记。对于中间的整块(i)们,调用后缀计数,将(cnT_{i,1-add_i})累加进结果即可。最终答案为两种结果加起来。(mathrm O!left(sz1+dfrac n{sz1} ight))

    此时令(sz1=leftlfloorsqrt n ight floor),加上重剖的(log)即可(mathrm O!left(qsqrt nlog n ight))完美滚粗。当然往死里卡还是能卡过去的,我才不会告诉你我曾经就卡过去了呢(

    接下来用出题人智商分析法。如果仅限于此,那么这题不就成了强行上树了?怎么还能成为集训队作业2018呢?所以这个重剖肯定有深藏不露之处。

    事实上:无论是对于区间修改还是区间查询,对整块处理的时间复杂度显然是整块的个数(mathrm O!left(dfrac n{sz1} ight))。一次分块维护(mathrm O!left(dfrac n{sz1} ight)),那么一条链是不是就需要(mathrm O!left(dfrac n{sz1}log n ight))了呢?不,不是。因为重剖出来的区间们不会有交集,那么总的整块的个数依然是(mathrm O!left(dfrac n{sz1} ight))级别的。于是一条链的复杂度就是(mathrm O!left(sz1cdotlog n+dfrac n{sz1} ight))。注意到两项乘积依然为常数,令(sz1cdotlog n=dfrac n{sz1})解得(sz1=sqrt{dfrac n{log n}})(令(sz1=leftlfloorsqrt{dfrac n{log_2n}} ight floor)),此时总时间复杂度为(mathrm O!left(qsqrt{nlog n} ight))

    然鹅这样空间复杂度为(mathrm O!left(qsqrt{nlog n} ight)),虽然ML很大但还是超过了一倍。注意到(cnT)的值们不会很大,用short存可以说完美,不多不少刚刚好。

    常数还是有点大,不过开个O3就AC了。

    代码:

    #pragma GCC optimize(3)
    #include<bits/stdc++.h>
    using namespace std;
    #define pb push_back
    const int N=100000,QU=100000,DB_SZ=1300;
    int n,qu;
    bool ol;
    vector<int> nei[N+1]; 
    int fa[N+1],sz[N+1],wson[N+1],dep[N+1],top[N+1],dfn[N+1],nowdfn,mxdfn[N+1];
    void dfs1(int x=1){//重剖预处理,下同 
    	sz[x]=1;
    	for(int i=0;i<nei[x].size();i++){
    		int y=nei[x][i];
    		if(y==fa[x])continue;
    		fa[y]=x;
    		dep[y]=dep[x]+1;
    		dfs1(y);
    		sz[x]+=sz[y];
    		if(sz[y]>sz[wson[x]])wson[x]=y;
    	}
    }
    void dfs2(int x=1,int t=1){
    	dfn[x]=mxdfn[x]=++nowdfn;
    	top[x]=t;
    	if(wson[x])dfs2(wson[x],t),mxdfn[x]=mxdfn[wson[x]];
    	for(int i=0;i<nei[x].size();i++){
    		int y=nei[x][i];
    		if(y==fa[x]||y==wson[x])continue;
    		dfs2(y,y);mxdfn[x]=mxdfn[y];
    	}
    }
    int a[N+1];
    struct dvdblk{//分块 
    	int sz,sz1;
    	struct block{int l,r,add;short cnT[2*QU+2];}blk[DB_SZ];
    	#define l(p) blk[p].l
    	#define r(p) blk[p].r
    	#define cnT(p) blk[p].cnT
    	#define add(p) blk[p].add
    	void bldblk(int p,int l,int r){//构造一个块 
    		l(p)=l;r(p)=r;
    		add(p)=0;
    		for(int i=l;i<=r;i++)cnT(p)[a[i]+qu]++;
    		for(int i=2*qu;~i;i--)cnT(p)[i]+=cnT(p)[i+1];
    	}
    	void init(){//分块初始化 
    		sz1=max(1,min(n,int(sqrt(n/max(1.,log2(n))))));
    //		printf("sz1=%d
    ",sz1);
    		sz=(n+sz1-1)/sz1;
    		for(int i=1;i<=sz;i++)bldblk(i,(i-1)*sz1+1,min(n,i*sz1));
    	}
    	void _add(int l,int r,int v){//区间修改 
    		int pl=(l+sz1-1)/sz1,pr=(r+sz1-1)/sz1;
    		if(pl==pr){//不满的块 
    			for(int i=l;i<=r;i++)
    				if(v==-1){if(a[i]>-qu)cnT(pl)[a[i]-- +qu]--;}
    				else{if(a[i]<qu+1)cnT(pl)[++a[i]+qu]++;}
    			return;
    		}
    		//整块 
    		for(int i=pl+1;i<pr;i++)add(i)+=v;
    		_add(l,r(pl),v);_add(l(pr),r,v);
    	}
    	int grt0(int l,int r){//区间查询 
    		int pl=(l+sz1-1)/sz1,pr=(r+sz1-1)/sz1;
    		if(pl==pr){//不满的块 
    			int res=0;
    			for(int i=l;i<=r;i++)res+=a[i]+add(pl)>0;
    //			cout<<l<<" "<<r<<":"<<res<<"
    ";
    			return res;
    		}
    		//整块 
    		int res=0;
    		for(int i=pl+1;i<pr;i++)res+=cnT(i)[max(-qu,min(qu+1,1-add(i)))+qu];
    		return res+grt0(l,r(pl))+grt0(l(pr),r);
    	}
    }db;
    void add_chn(int x,int y,int v){//链修改 
    	while(top[x]!=top[y]){
    		if(dep[top[x]]<dep[top[y]])swap(x,y);
    		db._add(dfn[top[x]],dfn[x],v);
    		x=fa[top[x]];
    	}
    	if(dep[x]<dep[y])swap(x,y);
    	db._add(dfn[y],dfn[x],v);
    }
    int grt0_chn(int x,int y){//链查询 
    	int res=0;
    	while(top[x]!=top[y]){
    		if(dep[top[x]]<dep[top[y]])swap(x,y);
    		res+=db.grt0(dfn[top[x]],dfn[x]);
    		x=fa[top[x]];
    	}
    	if(dep[x]<dep[y])swap(x,y);
    	return res+db.grt0(dfn[y],dfn[x]);
    }
    int grt0_subt(int x){return db.grt0(dfn[x],mxdfn[x]);}//子树查询 
    int main(){
    //	cout<<sizeof(db)/1024/1024;
    	cin>>n>>qu>>ol;
    	for(int i=1;i<n;i++){
    		int x,y;
    		scanf("%d%d",&x,&y);
    		nei[x].pb(y);nei[y].pb(x);
    	}
    	dfs1();dfs2();//重剖 
    	for(int i=1;i<=n;i++){
    		scanf("%d",a+dfn[i]);
    		a[dfn[i]]=max(-qu,min(qu+1,a[dfn[i]]));//限制值域 
    	}
    	db.init();//分块初始化 
    	int lasans=0;
    	for(int i=1;i<=qu;i++){
    		int tp,x,y,z;
    		scanf("%d%d",&tp,&x);ol&&(x^=lasans);
    		if(tp==1)scanf("%d%d",&y,&z),ol&&(y^=lasans),add_chn(x,y,z);
    		else if(tp==2)scanf("%d",&y),ol&&(y^=lasans),printf("%d
    ",lasans=grt0_chn(x,y));
    		else printf("%d
    ",lasans=grt0_subt(x));
    	}
    	return 0;
    }
    
  • 相关阅读:
    Unity 游戏框架搭建 2019 (二十九) 方法所在类命名问题诞生的原因
    Unity 游戏框架搭建 2019 (二十七、二十八)弃用的代码警告解决&弃用的代码删除
    Unity 游戏框架搭建 2019 (二十六) 第一轮整理完结
    Unity 游戏框架搭建 2019 (二十五) 类的第一个作用 与 Obselete 属性
    排序算法之冒泡排序
    java中List Array相互转换
    java迭代器浅析
    谈谈java中遍历Map的几种方法
    浅谈java反射机制
    springMvc注解之@ResponseBody和@RequestBody
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/UOJ-435.html
Copyright © 2020-2023  润新知