模拟线段树上的若干操作,然后查询相关的计数信息。

学习自 https://www.luogu.com/article/qh85y10k

区间定位数

定义 的区间定位数,为(广义)线段树上 最少拆为多少个区间的并。

区间 在线段树上的区间定位数等于 ,其中 完全包含的区间数。

P7143

对于 的标准线段树求所有区间的区间定位数之和。

前半部分拆开 算,后半部分 ,分治维护

int n;
map<int,int> cnt,sl,sr,slr,res;
void sovle(int n){
	if(cnt[n])return ;
	if(n==1){
		cnt[n]=sl[n]=sr[n]=slr[n]=res[n]=1;
		return ;
	}
	int mid=(n+1)>>1,vmid=mid%mod;
	sovle(mid),sovle(n-mid);
	cnt[n]=(cnt[mid]+cnt[n-mid]+1)%mod;
	sl[n]=(sl[mid]+sl[n-mid]+cnt[n-mid]*vmid+1)%mod;
	sr[n]=(sr[mid]+sr[n-mid]+cnt[n-mid]*vmid+n)%mod;
	slr[n]=(slr[mid]+slr[n-mid]+(sl[n-mid]+sr[n-mid])*vmid+cnt[n-mid]*vmid%mod*vmid+n)%mod;
	res[n]=((n%mod+1)*sl[n]+mod-slr[n])%mod;
}
int sum1(int n){return n*(n+1)/2%mod;}
int sum2(int n){return n*(n+1)%mod*(2*n+1)%mod*ni(6)%mod;}
void work(){
	n=read();sovle(n);
	int ans=2*(sum1(n%mod)*(n%mod+1)+mod-sum2(n%mod))%mod;
	printf("%lld\n",(ans+mod-res[n])%mod);
}

区间定位

(广义)线段树上, 的区间定位可以描述为:

取出 的 lca 的左儿子(不含)的链上是左儿子的节点的右兄弟。右边同理。

额外建出

P5210

对于 的广义线段树 次询问

。设 表示 的所有祖先的左偏/右偏儿子的右兄弟/左兄弟的数量/深度和,求出 lca 后差分求出。

对于 lca,分类讨论。如果 子树外,lca 为 。否则假设在 的左子树内,求出 的 lca 。对于 右子树中的区间 lca 为 ;左子树 一下的 lca 为 ;左子树 以上都是 的祖先,lca 为区间的父亲,特判 的右子树中时 的右儿子的 lca 不为 而是 的右儿子。

int n,idx,rt,q;
int ls[maxn],rs[maxn],fa[maxn],pos[maxn];
int dfs(int l,int r){
	if(l>r)return 0;
	if(l==r)return pos[l]=++idx;
	int id=++idx,p=read();
	ls[id]=dfs(l,p),rs[id]=dfs(p+1,r);
	return id;
}
int st[20][maxn],dfn[maxn],tim,dep[maxn],siz[maxn];
int cl[maxn],cr[maxn],sl[maxn],sr[maxn];
void dfs(int u){
	if(!u)return ;
	dep[u]=dep[fa[u]]+1;siz[u]=1;
	cl[u]=cl[fa[u]]+(u==ls[fa[u]])*(rs[fa[u]]!=0),sl[u]=sl[fa[u]]+(u==ls[fa[u]])*dep[u];
	cr[u]=cr[fa[u]]+(u==rs[fa[u]])*(ls[fa[u]]!=0),sr[u]=sr[fa[u]]+(u==rs[fa[u]])*dep[u];
	st[0][dfn[u]=++tim]=fa[u];
	dfs(ls[u]),dfs(rs[u]);
	siz[u]+=siz[ls[u]]+siz[rs[u]];
}
int mmax(int u,int v){return dfn[u]<dfn[v]?u:v;}
int lca(int u,int v){
	if(u==v)return u;
	u=dfn[u],v=dfn[v];
	if(u>v)swap(u,v);u++;
	int k=__lg(v-u+1);
	return mmax(st[k][u],st[k][v-(1<<k)+1]);
}
bool in(int u,int v){return dfn[u]<=dfn[v]&&dfn[v]<dfn[u]+siz[u];}
void work(){
	n=read();rt=dfs(1,n);
	pos[0]=++idx;ls[++idx]=pos[0];rs[idx]=rt;rt=idx;
	pos[n+1]=++idx;rs[++idx]=pos[n+1];ls[idx]=rt;rt=idx;
	for(int i=1;i<=idx;i++)fa[ls[i]]=fa[rs[i]]=i;fa[0]=0;
	dfs(rt);
	for(int j=1;j<20;j++){
		for(int i=1;i+(1<<j)-1<=idx;i++)st[j][i]=mmax(st[j-1][i],st[j-1][i+(1<<j-1)]);
	}
	q=read();
	while(q--){
		int u=read(),l=read(),r=read(),tp=lca(pos[l-1],pos[r+1]);
		int num=cl[pos[l-1]]-cl[ls[tp]]+cr[pos[r+1]]-cr[rs[tp]];
		int sum=sl[pos[l-1]]-sl[ls[tp]]+sr[pos[r+1]]-sr[rs[tp]];
		int ans=num*dep[u]+sum;
		if(!in(tp,u)||u==tp)ans-=2*dep[lca(u,tp)]*num;
		else if(in(ls[tp],u)){
			int x=lca(pos[l-1],u);
			ans-=2*dep[tp]*(cr[pos[r+1]]-cr[rs[tp]]);
			ans-=2*(sl[x]-sl[ls[tp]]-(cl[x]-cl[ls[tp]]));
			if(in(rs[x],u))ans-=2;
			ans-=2*dep[x]*(cl[pos[l-1]]-cl[x]);
		}
		else{
			int x=lca(pos[r+1],u);
			ans-=2*dep[tp]*(cl[pos[l-1]]-cl[ls[tp]]);
			ans-=2*(sr[x]-sr[rs[tp]]-(cr[x]-cr[rs[tp]]));
			if(in(ls[x],u))ans-=2;
			ans-=2*dep[x]*(cr[pos[r+1]]-cr[x]);
		}
		printf("%lld\n",ans);
	}
}

区间修改的 tag 下放

按对 做区间操作时 tag 的下放对节点的 tag 的不同影响将节点分为 类。

1.访问到并继续递归的结点。不管之前什么样,现在肯定没标记。

2.区间定位点。不管之前什么样,现在肯定有标记。

3. 的真后代。不变。

4. 的儿子,且不是 。如果有祖先或自己有标记则有标记。

5. 的真后代。不变。

维护 表示 节点/节点及祖先 有没有标记即可转移。

P5280

次操作。有 的概率对 做区间修改;询问期望有几个节点有 tag。

对于 类点,;对于 类点,;对于 类点,

线段树维护即可。可以维护 然后 ,维护除了几次

int n,q,ans;
int inv[maxn];
#define mid (l+r>>1)
#define ls nd<<1
#define rs nd<<1|1
int f[maxn<<2],g[maxn<<2],tag[maxn<<2];
void build(int nd,int l,int r){
	g[nd]=1;
	if(l==r)return ;
	build(ls,l,mid),build(rs,mid+1,r);
}
void inc(int &u,int v){((u+=v)>=mod)&&(u-=mod);}
void downf(int nd,int w){
	inc(ans,mod-f[nd]),f[nd]=(f[nd]+w)*inv[1]%mod,inc(ans,f[nd]);
}
void downg(int nd,int w){
	g[nd]=g[nd]*inv[w]%mod,tag[nd]+=w;
}
void down(int nd){
	downg(ls,tag[nd]),downg(rs,tag[nd]),tag[nd]=0;
}
void updata(int nd,int l,int r,int ql,int qr){
	if(l>=ql&&r<=qr){downf(nd,1),downg(nd,1);return ;}
	if(tag[nd])down(nd);
	if(ql<=mid)updata(ls,l,mid,ql,qr);
	else downf(ls,mod+1-g[ls]);
	if(qr>mid)updata(rs,mid+1,r,ql,qr);
	else downf(rs,mod+1-g[rs]);
	downf(nd,0);g[nd]=(g[nd]+1)*inv[1]%mod;
}
void work(){
	n=read();q=read();int mul=1;
	build(1,1,n);
	inv[0]=1,inv[1]=(mod+1)/2;for(int i=2;i<=q;i++)inv[i]=inv[i-1]*inv[1]%mod;
	while(q--){
		int op=read();
		if(op==1){
			mul=mul*2%mod;
			int l=read(),r=read();
			updata(1,1,n,l,r);
		}
		else printf("%lld\n",ans*mul%mod);
	}
}

P6630

次随机选取 做区间修改,询问最后期望有几个节点有 tag。

对于 类点,;对于 类点,;对于 类点,;对于 类点,;对于 类点,

对于区间 ,父亲为 ,选取 使得 成为某类点的方案数分别为。记 。成为 类点,;成为 类点,;成为 类点,;成为 类点,;成为 类点,

维护 矩阵。

int n,k,ans,inv;
int val(int n){return n*(n+1)/2%mod;}
void calc(int l,int r,int fl,int fr){
	mat a;
	a.e[2][2]=1;
	int p1=(val(n)-val(l-1)-val(n-r)-l*(n-r+1)%mod+3*mod)*inv%mod;
	int p2=(l*(n-r+1)%mod-fl*(n-fr+1)%mod+mod)*inv%mod;
	int p3=fl*(n-fr+1)%mod*inv%mod;
	int p4=(val(l-1)+val(n-r)-val(fl-1)-val(n-fr)+2*mod)*inv%mod;
	int p5=(val(fl-1)+val(n-fr))*inv%mod;
	// assert((p1+p2+p3+p4+p5)%mod==1);
	(a.e[2][0]+=p2)%=mod;
	(a.e[2][1]+=p2)%=mod;
	(a.e[0][0]+=p3)%=mod;
	(a.e[2][1]+=p3)%=mod;
	(a.e[1][0]+=p4)%=mod;
	(a.e[1][1]+=p4)%=mod;
	(a.e[0][0]+=p5)%=mod;
	(a.e[1][1]+=p5)%=mod;
	a=ksm(a,k);
	(ans+=a.e[2][0])%=mod;
}
void dfs(int l,int r,int fl,int fr){
	if(l>r)return ;
	calc(l,r,fl,fr);
	if(l==r)return ;
	int p=read();
	dfs(l,p,l,r),dfs(p+1,r,l,r);
}
void work(){
	n=read();k=read();inv=ni(val(n));
	dfs(1,n,0,n);
	printf("%lld\n",ans);
}