【做题记录】线段树(马思博)

SP1043 GSS1 - Can you answer these queries I

猫树——线性对数预处理,常数查询的优秀数据结构。

其实它是线段树的一个变种。考虑线段树的每个节点 \([L,R]\),对于 \(i\in[L,mid]\) 维护 \([i,mid]\) 的信息,对于 \(i\in[mid+1,R]\) 维护 \([mid+1,i]\) 的信息。于是对于查询 \([l,r]\),我们只需要找到包含这个区间的最小的节点即可 \(O(1)\) 查询。问题变为如何快速求出这个节点,不难发现它就是 \([l,l]\)\([r,r]\) 的 LCA。我们还可以发现,线段树上同一层的两个节点 \(x\)\(y\) 的 LCA 即为它们二进制下的最长公共前缀,也就是 \(x\operatorname{rsh}(\log(x\oplus y)+1)\)。于是我们把 \(n\) 补全成最小的 \(2^k\),使所有叶子节点都在同一层即可。具体实现时我们不考虑 LCA 的编号,只需求出它在哪一层。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=7e4+5;
int n,m,a[maxn],Log[maxn];
struct node{
	int sum,mm,lm,rm;
	node(int sum,int mm,int lm,int rm)
		:sum(sum),mm(mm),lm(lm),rm(rm){}
	node(int x=0):sum(x),mm(x),lm(x),rm(x){}
	il node operator+(const node &x)const{
		return node(sum+x.sum,max({mm,x.mm,rm+x.lm}),max(lm,sum+x.lm),max(x.rm,x.sum+rm));
	}
}tr[17][maxn];
il void build(int l,int r,int d){
	if(l==r){
		tr[d][l]=node(a[l]);
		return ;
	}
	int mid=(l+r)>>1;
	tr[d][mid]=node(a[mid]);
	for(int i=mid-1;i>=l;i--){
		tr[d][i]=node(a[i])+tr[d][i+1];
	}
	tr[d][mid+1]=node(a[mid+1]);
	for(int i=mid+2;i<=r;i++){
		tr[d][i]=tr[d][i-1]+node(a[i]);
	}
	build(l,mid,d+1);
	build(mid+1,r,d+1);
}
il int solve(int l,int r){
	if(l==r){
		return a[l];
	}
	int d=15-Log[(65535+l)^(65535+r)];
	return (tr[d][l]+tr[d][r]).mm;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	n=65536;
	for(int i=2;i<=n;i++){
		Log[i]=Log[i>>1]+1;
	}
	build(1,n,0);
	cin>>m;
	while(m--){
		int l,r;
		cin>>l>>r;
		cout<<solve(l,r)<<"\n";
	}
	return 0;
}
}
int main(){return asbt::main();}

G. 好吃的题目

将询问离线下来,不建出猫树,在递归过程中处理询问,即为猫树分治。

对于此题,考虑当前所有询问区间的 \([l_i,r_i]\subseteq[L,R]\),求出 \(mid=\lfloor\frac{L+R}{2}\rfloor\),对于 \(i\in[L,mid]\) 背包 DP 出 \(f_{i,j}\) 表示 \([i,mid]\)\(\le j\) 的热量的最大美味度,对于 \(i\in[mid+1,R]\) 求出 \([mid+1,i]\) 对应的答案。对于 \(l_i\in[L,mid]\land r_i\in[mid+1,R]\) 直接 \(O(t_i)\) 计算答案,对于其他询问递归处理即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=4e4+5,maxm=2e5+5;
int n,m,cnt,a[maxn];
ll b[maxn],ans[maxm],f[maxn][205];
struct{
	int l,r,v,id;
}q[maxm],hp[maxm];
il void solve(int L,int R,int l,int r){
	if(l>r){
		return ;
	}
	int mid=(L+R)>>1;
	for(int i=0;i<=200;i++){
		f[mid][i]=(i>=a[mid])*b[mid];
		f[mid+1][i]=(i>=a[mid+1])*b[mid+1];
	}
	for(int i=mid-1;i>=L;i--){
		for(int j=0;j<=200;j++){
			f[i][j]=f[i+1][j];
			if(j>=a[i]){
				f[i][j]=max(f[i][j],f[i+1][j-a[i]]+b[i]);
			}
		}
	}
	for(int i=mid+2;i<=R;i++){
		for(int j=0;j<=200;j++){
			f[i][j]=f[i-1][j];
			if(j>=a[i]){
				f[i][j]=max(f[i][j],f[i-1][j-a[i]]+b[i]);
			}
		}
	}
	int pl=l,pr=r;
	for(int i=l;i<=r;i++){
		if(q[i].l<=mid&&q[i].r>mid){
			for(int j=0;j<=q[i].v;j++){
				ans[q[i].id]=max(ans[q[i].id],f[q[i].l][j]+f[q[i].r][q[i].v-j]);
			}
		}
		else if(q[i].r<=mid){
			hp[pl++]=q[i];
		}
		else{
			hp[pr--]=q[i];
		}
	}
	for(int i=l;i<=r;i++){
		q[i]=hp[i];
	}
	solve(L,mid,l,pl-1);
	solve(mid+1,R,pr+1,r);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	for(int i=1,l,r,v;i<=m;i++){
		cin>>l>>r>>v;
		if(l==r){
			if(v>=a[l]){
				ans[i]=b[l];
			}
		}
		else{
			q[++cnt]={l,r,v,i};
		}
	}
	solve(1,n,1,cnt);
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<"\n";
	}
	return 0;
}
}
int main(){return asbt::main();}

uoj228 基础数据结构练习题

考虑两种操作对区间极差的影响:加法操作后极差不变,开根操作后极差变小。使用线段树,考虑较难维护的开根操作,如果这个区间极差为 \(0\) 那么可以直接转化为加法操作,否则我们直接暴力递归左右子区间。考虑时间复杂度,发现每个区间经过至多 \(5\) 次开根操作后极差就会变为 \(0\),因此是正确的。

然而有一个特殊情况可以把这个算法卡爆,那就是开根后极差不变。容易证明这只有一个情况,那就是 \(k^2-1\)\(k^2\)。发现此时开根后它们的变化量也是一样的,于是特判掉即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m;
ll a[maxn],sum[maxn<<2],mx[maxn<<2],mn[maxn<<2],tag[maxn<<2];
il void pushup(int id){
	sum[id]=sum[lid]+sum[rid];
	mx[id]=max(mx[lid],mx[rid]);
	mn[id]=min(mn[lid],mn[rid]);
}
il void pushtag(int id,int l,int r,ll v){
	sum[id]+=(r-l+1)*v;
	mx[id]+=v,mn[id]+=v,tag[id]+=v;
}
il void pushdown(int id,int l,int r){
	if(tag[id]){
		int mid=(l+r)>>1;
		pushtag(lid,l,mid,tag[id]);
		pushtag(rid,mid+1,r,tag[id]);
		tag[id]=0;
	}
}
il void build(int id,int l,int r){
	if(l==r){
		sum[id]=mx[id]=mn[id]=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void add(int id,int L,int R,int l,int r,ll v){
	if(L>=l&&R<=r){
		pushtag(id,L,R,v);
		return ;
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(l<=mid){
		add(lid,L,mid,l,r,v);
	}
	if(r>mid){
		add(rid,mid+1,R,l,r,v);
	}
	pushup(id);
}
il void pfg(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		ll sq=sqrt(mx[id]);
		if(mx[id]==mn[id]||mx[id]-mn[id]==1&&sq*sq==mx[id]){
			pushtag(id,L,R,sq-mx[id]);
			return ;
		}
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(l<=mid){
		pfg(lid,L,mid,l,r);
	}
	if(r>mid){
		pfg(rid,mid+1,R,l,r);
	}
	pushup(id);
}
il ll query(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		return sum[id];
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	ll res=0;
	if(l<=mid){
		res+=query(lid,L,mid,l,r);
	}
	if(r>mid){
		res+=query(rid,mid+1,R,l,r);
	}
	return res;
}
//il void debug(int id,int l,int r){
//	cout<<l<<" "<<r<<" "<<sum[id]<<" "<<mx[id]<<" "<<mn[id]<<" "<<tag[id]<<"\n";
//	if(l==r){
//		return ;
//	}
//	pushdown(id,l,r);
//	int mid=(l+r)>>1;
//	debug(lid,l,mid);
//	debug(rid,mid+1,r);
//}
int main(){
//	system("fc ex_data2.out my.out");
//	freopen("ex_data2.in","r",stdin);
//	freopen("my.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,1,n);
//	debug(1,1,n);
	while(m--){
		int opt,l,r;
		cin>>opt>>l>>r;
		switch(opt){
			case 1:{
				ll x;
				cin>>x;
				add(1,1,n,l,r,x);
				break;
			}
			case 2:{
				pfg(1,1,n,l,r);
				break;
			}
			default:{
				cout<<query(1,1,n,l,r)<<"\n";
				break;
			}
		}
//		debug(1,1,n);
	}
	return 0;
}
}
int main(){return asbt::main();}

O. [TJOI2016 & HEOI2016] 排序

考虑二分答案,将 \(\ge mid\) 的设为 \(1\)\(<mid\) 的设为 \(-1\),于是区间排序操作就转化为了区间查询+推平操作,线段树维护即可。时间复杂度线性对数方。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,q,a[maxn],cnt[maxn<<2],tag[maxn<<2];
struct{
	int opt,l,r;
}b[maxn];
il void pushup(int id){
	cnt[id]=cnt[lid]+cnt[rid];
}
il void pushtag(int id,int l,int r,int x){
	tag[id]=x;
	cnt[id]=x==1?r-l+1:0;
}
il void pushdown(int id,int l,int r){
	if(tag[id]){
		int mid=(l+r)>>1;
		pushtag(lid,l,mid,tag[id]);
		pushtag(rid,mid+1,r,tag[id]);
		tag[id]=0;
	}
}
il void build(int id,int l,int r,int x){
	tag[id]=0;
	if(l==r){
		pushtag(id,l,r,a[l]>=x?1:-1);
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid,x);
	build(rid,mid+1,r,x);
	pushup(id);
}
il void upd(int id,int L,int R,int l,int r,int x){
	if(l>r){
		return ;
	}
	if(L>=l&&R<=r){
		pushtag(id,L,R,x);
		return ;
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(l<=mid){
		upd(lid,L,mid,l,r,x);
	}
	if(r>mid){
		upd(rid,mid+1,R,l,r,x);
	}
	pushup(id);
}
il int query(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		return cnt[id];
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1,res=0;
	if(l<=mid){
		res+=query(lid,L,mid,l,r);
	}
	if(r>mid){
		res+=query(rid,mid+1,R,l,r);
	}
	return res;
}
il bool check(int x){
	build(1,1,n,x);
	for(int i=1,opt,l,r;i<=m;i++){
		opt=b[i].opt,l=b[i].l,r=b[i].r;
		int cnt=query(1,1,n,l,r);
		if(opt){
			upd(1,1,n,l,l+cnt-1,1);
			upd(1,1,n,l+cnt,r,-1);
		}
		else{
			upd(1,1,n,r-cnt+1,r,1);
			upd(1,1,n,l,r-cnt,-1);
		}
	}
	return query(1,1,n,q,q)==1;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1,opt,l,r;i<=m;i++){
		cin>>opt>>l>>r;
		b[i]={opt,l,r};
	}
	cin>>q;
	int l=1,r=n;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(check(mid)){
			l=mid;
		}
		else{
			r=mid-1;
		}
	}
	cout<<l;
	return 0;
}
}
int main(){return asbt::main();}

P. [WC2005] 双面棋盘

一个暴力的想法是并查集,每次操作直接全部重构,时间复杂度是 \(O(n^2m)\) 的。

考虑以行号为下标建线段树,对于每个节点记录它对应的第一行和最后一行在只考虑这些格子时在并查集中属于哪个连通块。考虑合并,显然我们要考虑上面那一块的最后一行和下面那一块的第一行,重建并查集合并即可。时间复杂度 \(O(nm\log n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
#define gt(x,y) (((x)-1)*n+(y))
using namespace std;
namespace asbt{
int n,m,a[205][205],fa[40005];
int tr[805][2][205],cnt[805][2];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void pushup(int id,int l,int r){
	for(int i=1;i<=n;i++){
		fa[tr[lid][0][i]]=tr[lid][0][i];
		fa[tr[lid][1][i]]=tr[lid][1][i];
		fa[tr[rid][0][i]]=tr[rid][0][i];
		fa[tr[rid][1][i]]=tr[rid][1][i];
	}
	cnt[id][0]=cnt[lid][0]+cnt[rid][0];
	cnt[id][1]=cnt[lid][1]+cnt[rid][1];
	int mid=(l+r)>>1;
	for(int i=1;i<=n;i++){
		if(a[mid][i]==a[mid+1][i]){
			int u=find(tr[lid][1][i]),v=find(tr[rid][0][i]);
			if(u!=v){
				fa[u]=v,cnt[id][a[mid][i]]--;
//				cout<<"pushup: "<<i<<'\n';
			}
		}
	}
	for(int i=1;i<=n;i++){
		tr[id][0][i]=find(tr[lid][0][i]);
		tr[id][1][i]=find(tr[rid][1][i]);
	}
}
il void build(int id,int l,int r){
	if(l==r){
		cnt[id][0]=cnt[id][1]=0;
		for(int i=1;i<=n;i++){
			fa[gt(l,i)]=gt(l,i);
			cnt[id][a[l][i]]++;
		}
		for(int i=1;i<=n;i++){
			if(i>1&&a[l][i]==a[l][i-1]){
				cnt[id][a[l][i]]--;
				fa[gt(l,i)]=fa[gt(l,i-1)];
			}
			tr[id][0][i]=tr[id][1][i]=fa[gt(l,i)];
		}
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
//	cout<<l<<' '<<r<<'\n';
	pushup(id,l,r);
}
il void upd(int id,int l,int r,int p){
	if(l==r){
		cnt[id][0]=cnt[id][1]=0;
		for(int i=1;i<=n;i++){
			fa[gt(l,i)]=gt(l,i);
			cnt[id][a[l][i]]++;
		}
		for(int i=2;i<=n;i++){
			if(a[l][i]==a[l][i-1]){
				cnt[id][a[l][i]]--;
				fa[gt(l,i)]=fa[gt(l,i-1)];
			}
			tr[id][0][i]=tr[id][1][i]=fa[gt(l,i)];
		}
		return ;
	}
	int mid=(l+r)>>1;
	if(p<=mid){
		upd(lid,l,mid,p);
	}
	else{
		upd(rid,mid+1,r,p);
	}
	pushup(id,l,r);
}
//il void debug(int id,int l,int r){
//	cout<<l<<' '<<r<<' '<<cnt[id][1]<<' '<<cnt[id][0]<<'\n';
//	if(l==r){
//		return ;
//	}
//	int mid=(l+r)>>1;
//	debug(lid,l,mid);
//	debug(rid,mid+1,r);
//}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
		}
	}
	build(1,1,n);
//	debug(1,1,n);
	cin>>m;
	while(m--){
		int x,y;
		cin>>x>>y;
		a[x][y]^=1;
		upd(1,1,n,x);
		cout<<cnt[1][1]<<' '<<cnt[1][0]<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

Q. The Tree

考虑每次 1 u 操作给 \(u\) 的权值加一,如果不考虑二操作,查询时检查 \(u\) 的根链上有没有一个后缀和等于这一段的长度即可。可以将每个位置初始值设为 \(-1\),于是维护最大后缀和即可。

考虑 \(2\) 操作,我们首先要将 \(u\) 的子树推平成 \(-1\),但是这样不完全,因为我们还没有考虑 \(u\) 根链上操作的影响。因此我们需要给 \(u\) 的权值再减去一个 \(v\),使得 \(u\) 的根链最大后缀和为 \(-1\)。显然 \(v=\operatorname{query}(u)+1\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,sz[maxn],hes[maxn];
int cnt,dfn[maxn],top[maxn];
int fa[maxn],tag[maxn<<2];
vector<int> e[maxn];
struct node{
	int sum,suf;
	node(int sum=0,int suf=0):sum(sum),suf(suf){}
	il node operator+(const node &x)const{
		return node(sum+x.sum,max(x.suf,x.sum+suf));
	}
}tr[maxn<<2];
il void dfs1(int u){
	sz[u]=1;
	int mxs=0;
	for(int v:e[u]){
		fa[v]=u;
		dfs1(v);
		sz[u]+=sz[v];
		if(mxs<sz[v]){
			mxs=sz[v],hes[u]=v;
		}
	}
}
il void dfs2(int u){
	dfn[u]=++cnt;
	if(!top[u]){
		top[u]=u;
	}
	if(hes[u]){
		top[hes[u]]=top[u];
		dfs2(hes[u]);
	}
	for(int v:e[u]){
		if(v==hes[u]){
			continue;
		}
		dfs2(v);
	}
}
il void pushup(int id){
	tr[id]=tr[lid]+tr[rid];
}
il void pushtag(int id,int l,int r){
	tag[id]=1;
	tr[id]=node(l-r-1,-1);
}
il void pushdown(int id,int l,int r){
	if(tag[id]){
		int mid=(l+r)>>1;
		pushtag(lid,l,mid);
		pushtag(rid,mid+1,r);
		tag[id]=0;
	}
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id]=node(-1,-1);
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void add(int id,int l,int r,int p,int v){
	if(l==r){
		tr[id].sum+=v,tr[id].suf+=v;
		return ;
	}
	pushdown(id,l,r);
	int mid=(l+r)>>1;
	if(p<=mid){
		add(lid,l,mid,p,v);
	}
	else{
		add(rid,mid+1,r,p,v);
	}
	pushup(id);
}
il void upd(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		pushtag(id,L,R);
		return ;
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(l<=mid){
		upd(lid,L,mid,l,r);
	}
	if(r>mid){
		upd(rid,mid+1,R,l,r);
	}
	pushup(id);
}
il node query(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		return tr[id];
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(r<=mid){
		return query(lid,L,mid,l,r);
	}
	if(l>mid){
		return query(rid,mid+1,R,l,r);
	}
	return query(lid,L,mid,l,r)+query(rid,mid+1,R,l,r);
}
il int query(int u){
	node res=query(1,1,n,dfn[top[u]],dfn[u]);
	u=fa[top[u]];
	while(u){
		res=query(1,1,n,dfn[top[u]],dfn[u])+res;
		u=fa[top[u]];
	}
	return res.suf;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=2,x;i<=n;i++){
		cin>>x;
		e[x].pb(i);
	}
	dfs1(1),dfs2(1);
	build(1,1,n);
	while(m--){
		int opt,u;
		cin>>opt>>u;
		switch(opt){
			case 1:{
				add(1,1,n,dfn[u],1);
				break;
			}
			case 2:{
				upd(1,1,n,dfn[u],dfn[u]+sz[u]-1);
				add(1,1,n,dfn[u],-query(u)-1);
				break;
			}
			default:{
				cout<<(query(u)>=0?"black":"white")<<'\n';
				break;
			}
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

R. [CEOI 2019] Dynamic Diameter

考虑线段树维护直径。考虑修改 \((u\xrightarrow{w}v)\to(u\xrightarrow{w'}v)\),会将 \(v\) 的子树中的每个点的 \(dis\) 值改变,把 dfs 序拉下来树状数组维护即可。然后 \(v\) 子树内的直径是不变的,在线段树上找到这个区间更新上去即可。时间复杂度线性对数方。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,kk,fa[maxn],dfn[maxn],stk[maxn],cnt,sz[maxn],fe[maxn];
vector<int> e[maxn];
struct{
	int u,v,w;
}E[maxn];
il void dfs(int u,int fth){
	fa[u]=fth,sz[u]=1;
	dfn[u]=++cnt,stk[cnt]=u;
	for(int v:e[u]){
		if(v==fth){
			continue;
		}
		dfs(v,u);
		sz[u]+=sz[v];
	}
}
struct{
	#define lowbit(x) (x&-x)
	int tr[maxn];
	il void add(int p,int v){
		for(;p<=n;p+=lowbit(p)){
			tr[p]+=v;
		}
	}
	il int query(int p){
		int res=0;
		for(;p;p-=lowbit(p)){
			res+=tr[p];
		}
		return res;
	}
	#undef lowbit
}F;
namespace LCA{
	int df[maxn],oula[maxn<<1],idx[maxn<<1],cnt;
	il void dfs(int u,int fa){
		df[u]=++cnt,oula[cnt]=cnt,idx[cnt]=u;
		for(int v:e[u]){
			if(v==fa){
				continue;
			}
			dfs(v,u);
			oula[++cnt]=df[u];
		}
	}
	struct{
		int Log[maxn<<1],st[maxn<<1][19];
		il void build(){
			for(int i=2;i<=cnt;i++){
				Log[i]=Log[i>>1]+1;
			}
			for(int i=1;i<=cnt;i++){
				st[i][0]=oula[i];
			}
			for(int j=1;j<=Log[cnt];j++){
				for(int i=1;i+(1<<j)-1<=cnt;i++){
					st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
				}
			}
		}
		il int query(int l,int r){
			int p=Log[r-l+1];
			return min(st[l][p],st[r-(1<<p)+1][p]);
		}
	}ST;
	il void init(){
		dfs(1,0),ST.build();
	}
	il int lca(int u,int v){
		if(df[u]>df[v]){
			swap(u,v);
		}
		return idx[ST.query(df[u],df[v])];
	}
	il int dis(int u,int v){
		return F.query(dfn[u])+F.query(dfn[v])-F.query(dfn[lca(u,v)])*2;
	}
}
using LCA::dis;
struct node{
	int u,v,w;
	node(int u=0,int v=0,int w=0):u(u),v(v),w(w){}
	il node operator+(const node &x)const{
		int hp[4]={u,v,x.u,x.v};
		node res;
		for(int i=0;i<=3;i++){
			for(int j=i+1;j<=3;j++){
				int tmp=dis(hp[i],hp[j]);
				if(tmp>res.w){
					res=node(hp[i],hp[j],tmp);
				}
			}
		}
		return res;
	}
}tr[maxn<<2];
il void pushup(int id){
	tr[id]=tr[lid]+tr[rid];
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id]=node(stk[l],stk[l],0);
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void upd(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		return ;
	}
	int mid=(L+R)>>1;
	if(l<=mid){
		upd(lid,L,mid,l,r);
	}
	if(r>mid){
		upd(rid,mid+1,R,l,r);
	}
	pushup(id);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>kk;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		e[u].pb(v),e[v].pb(u);
		E[i]={u,v,w};
	}
	dfs(1,0),LCA::init();
	for(int i=1;i<n;i++){
		int &u=E[i].u,&v=E[i].v,w=E[i].w;
		if(fa[v]==u){
			swap(u,v);
		}
		fe[u]=w;
		F.add(dfn[u],w),F.add(dfn[u]+sz[u],-w);
	}
	build(1,1,n);
	int ans=0;
	while(m--){
		int x,v;
		cin>>x>>v;
		x=(x+ans)%(n-1)+1;
		v=(v+ans)%kk;
		int u=E[x].u;
		F.add(dfn[u],v-fe[u]),F.add(dfn[u]+sz[u],fe[u]-v);
		fe[u]=v;
		upd(1,1,n,dfn[u],dfn[u]+sz[u]-1);
		cout<<(ans=tr[1].w)<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

S. [JSOI2009] 面试的考验

三倍经验

首先考虑将所有点对的答案都存起来,于是就变成了一个二维数点问题,扫描线 + 树状数组维护即可。

但我们显然存不下 \(O(n^2)\) 的点对,考虑减少点对数量。不妨令点对 \((i,j)\) 满足 \(i<j\land a_i<a_j\)\(a_i>a_j\) 的情况翻转过来再算一遍即可。对于每个 \(i\) 在它后面的第一个能贡献的 \(j\) 一定满足 \(a_j\in[a_i+1,+\infty)\)\(j\) 之后能再与 \(i\) 产生贡献的 \(k\) 一定满足 \(k\in[a_i+1,\frac{a_i+a_j}{2}]\),因为如果更大那么与 \(j\) 产生贡献一定是更优的。于是每个 \(i\) 对应的 \(j\) 就是 \(O(\log V)\) 个,点对总数就是 \(O(n\log V)\) 个。找点对用权值线段树倒着维护一下最小出现位置即可,时间复杂度线性对数方。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5,inf=1e9;
int n,m,a[maxn],ans[maxn];
struct node{
	int l,r,id;
	il bool operator<(const node &x)const{
		return l>x.l;
	}
}b[maxn];
struct{
	int rt,tot,ls[maxn<<5],rs[maxn<<5],tr[maxn<<5];
	il void pushup(int id){
		tr[id]=min(tr[ls[id]],tr[rs[id]]);
	}
	il void build(){
		rt=tot=0,ls[0]=rs[0]=0,tr[0]=inf;
	}
	il void upd(int &id,int l,int r,int p,int v){
		if(!id){
			id=++tot;
			ls[id]=rs[id]=0,tr[id]=inf;
		}
		if(l==r){
			tr[id]=min(tr[id],v);
			return ;
		}
		int mid=(l+r)>>1;
		if(p<=mid){
			upd(ls[id],l,mid,p,v);
		}
		else{
			upd(rs[id],mid+1,r,p,v);
		}
		pushup(id);
	}
	il int query(int id,int L,int R,int l,int r){
		if(!id){
			return inf;
		}
		if(L>=l&&R<=r){
			return tr[id];
		}
		int mid=(L+R)>>1,res=inf;
		if(l<=mid){
			res=min(res,query(ls[id],L,mid,l,r));
		}
		if(r>mid){
			res=min(res,query(rs[id],mid+1,R,l,r));
		}
		return res;
	}
	il void upd(int p,int v){
		upd(rt,1,inf,p,v);
	}
	il int query(int l,int r){
		return query(rt,1,inf,l,r);
	}
}S;
struct{
	#define lowbit(x) (x&-x)
	int tr[maxn];
	il void init(){
		memset(tr,0x3f,sizeof(tr));
	}
	il void upd(int p,int v){
		for(;p<=n;p+=lowbit(p)){
			tr[p]=min(tr[p],v);
		}
	}
	il int query(int p){
		int res=inf;
		for(;p;p-=lowbit(p)){
			res=min(res,tr[p]);
		}
		return res;
	}
	#undef lowbit
}F;
il void solve(){
	vector<pii> vec;
	S.build(),F.init();
	for(int i=n;i;i--){
		int x=inf;
		while(x>a[i]){
			int y=S.query(a[i]+1,x);
			if(y==inf){
				break;
			}
			vec.pb(mp(i,y));
			x=(a[i]+a[y])>>1;
		}
		S.upd(a[i],i);
	}
	sort(b+1,b+m+1);
	for(int i=1,j=0;i<=m;i++){
		for(;j<vec.size()&&vec[j].fir>=b[i].l;j++){
			F.upd(vec[j].sec,a[vec[j].sec]-a[vec[j].fir]);
		}
		ans[b[i].id]=min(ans[b[i].id],F.query(b[i].r));
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=m;i++){
		cin>>b[i].l>>b[i].r;
		b[i].id=i;
	}
	memset(ans,0x3f,sizeof(ans));
	solve();
	reverse(a+1,a+n+1);
	for(int i=1;i<=m;i++){
		swap(b[i].l,b[i].r);
		b[i].l=n-b[i].l+1;
		b[i].r=n-b[i].r+1;
	}
	solve();
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

T. [agc001_f]Wide Swap

考虑 \(P\) 的逆置换 \(Q\),于是操作变成了当 \(|Q_i-Q_{i+1}|\ge K\) 时可以交换 \(i\)\(i+1\)。考虑如果 \(|Q_i-Q_j|<K\),那么它们两个一定交换不了。回到 \(P\),也就是说如果 \(|i-j|<K\land P_i<P_j\),那么最终的答案中一定也有 \(P_i<P_j\)。将所有的 \(i\)\(j\) 连边,倒着跑个拓扑排序即可。但是边数太多,考虑优化。显然 \(i\) 度数为 \(0\) 等价于 \(P_i\)\([i-K+1,i+K-1]\) 中的最大值,线段树即可,删去一个点时把 \(P_i\) 改成 \(-\infty\) 就好了。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=5e5+5,inf=1e9;
int n,m,a[maxn],tr[maxn<<2],ans[maxn],cnt;
bool inq[maxn];
priority_queue<int> q;
il int mge(int x,int y){
	return a[x]>a[y]?x:y;
}
il void pushup(int id){
	tr[id]=mge(tr[lid],tr[rid]);
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id]=l;
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void upd(int id,int l,int r,int p){
	if(l==r){
		tr[id]=0;
		return ;
	}
	int mid=(l+r)>>1;
	if(p<=mid){
		upd(lid,l,mid,p);
	}
	else{
		upd(rid,mid+1,r,p);
	}
	pushup(id);
}
il int query(int id,int L,int R,int l,int r){
	if(l>r){
		return 0;
	}
	if(L>=l&&R<=r){
		return tr[id];
	}
	int mid=(L+R)>>1;
	if(r<=mid){
		return query(lid,L,mid,l,r);
	}
	if(l>mid){
		return query(rid,mid+1,R,l,r);
	}
	return mge(query(lid,L,mid,l,r),query(rid,mid+1,R,l,r));
}
il void chk(int p){
	if(inq[p]){
		return ;
	}
	if(query(1,1,n,max(p-m+1,1),min(p+m-1,n))==p){
		q.push(p),inq[p]=1;
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	a[0]=-inf;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,1,n);
	for(int i=1;i<=n;i++){
		chk(i);
	}
	cnt=n;
	while(q.size()){
		int u=q.top();q.pop();
		ans[u]=cnt--;
		upd(1,1,n,u);
		int v;
		if(v=query(1,1,n,max(u-m+1,1),u-1)){
			chk(v);
		}
		if(v=query(1,1,n,u+1,min(u+m-1,n))){
			chk(v);
		}
	}
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

U. [APIO2018] 新家

首先对时间维做扫描线。

考虑二分答案,现在的问题是求出 \([x-mid,x+mid]\) 中是否有 \(m\) 个颜色。发现并不好求,考虑设 \(pre_i\) 表示 \(i\) 左侧第一个与 \(i\) 颜色相同的位置,于是 \((pre_i,i)\) 之间没有这个颜色。于是我们用线段树维护每个位置的 \(pre\),查询 \(\min[x+mid+1,+\infty)\)\(x-mid\) 的关系即可。至于 \(pre\) 的计算,给每个颜色都开一个 multiset 即可。但是每个位置可能同时有很多个商店,因此对线段树的每个叶子节点也要开一个 multiset。时间复杂度线性对数方。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lwrb lower_bound
using namespace std;
namespace asbt{
const int maxn=3e5+5,inf=1e9,mx=1e8+1;
int n,m,q,rt,tot,cnt,lsh[maxn],vis[maxn];
int ls[maxn*30],rs[maxn*30],tr[maxn*30],ans[maxn];
struct oprt{
	int tim,opt,typ,loc;
	oprt(int tim=0,int opt=0,int typ=0,int loc=0)
		:tim(tim),opt(opt),typ(typ),loc(loc){}
	il bool operator<(const oprt &x)const{
		return tim<x.tim||tim==x.tim&&opt<x.opt;
	}
}a[maxn*3];
multiset<int> pos[maxn],pre[maxn];
il void pushup(int id){
	tr[id]=min(tr[ls[id]],tr[rs[id]]);
}
il void insert(int &id,int l,int r,int x,int v){
	if(!id){
		id=++tot;
	}
	if(l==r){
		int p=lwrb(lsh+1,lsh+cnt+1,x)-lsh;
		if(pre[p].empty()){
			pre[p].insert(inf);
		}
		pre[p].insert(v);
		tr[id]=*pre[p].begin();
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid){
		insert(ls[id],l,mid,x,v);
	}else{
		insert(rs[id],mid+1,r,x,v);
	}
	pushup(id);
}
il void erase(int id,int l,int r,int x,int v){
	if(l==r){
		int p=lwrb(lsh+1,lsh+cnt+1,x)-lsh;
		pre[p].erase(pre[p].find(v));
		tr[id]=*pre[p].begin();
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid){
		erase(ls[id],l,mid,x,v);
	}else{
		erase(rs[id],mid+1,r,x,v);
	}
	pushup(id);
}
il int query(int id,int L,int R,int l,int r){
	if(!id){
		return inf;
	}
	if(L>=l&&R<=r){
		return tr[id];
	}
	int mid=(L+R)>>1,res=inf;
	if(l<=mid){
		res=min(res,query(ls[id],L,mid,l,r));
	}
	if(r>mid){
		res=min(res,query(rs[id],mid+1,R,l,r));
	}
	return res;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>q;
	int tot=0;
	for(int i=1,x,t,l,r;i<=n;i++){
		cin>>x>>t>>l>>r;
		a[++tot]=oprt(l,0,t,x);
		a[++tot]=oprt(r,2,t,x);
		lsh[++cnt]=x;
	}
	lsh[++cnt]=mx;
	sort(lsh+1,lsh+cnt+1);
	cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;
	for(int i=1,l,y;i<=q;i++){
		cin>>l>>y;
		a[++tot]=oprt(y,1,i,l);
	}
	tr[0]=inf;
	for(int i=1;i<=m;i++){
		pos[i].insert(0);
		pos[i].insert(mx);
		insert(rt,1,mx,mx,0);
	}
	sort(a+1,a+tot+1);
	int num=0;
	for(int i=1;i<=tot;i++){
//		cout<<a[i].tim<<' '<<a[i].opt<<' '<<a[i].typ<<' '<<a[i].loc<<'\n';
		int t=a[i].typ,x=a[i].loc;
		switch(a[i].opt){
			case 0:{
				if(++vis[t]==1){
					num++;
				}
				auto it=pos[t].insert(x);
				insert(rt,1,mx,x,*prev(it));
				erase(rt,1,mx,*next(it),*prev(it));
				insert(rt,1,mx,*next(it),x);
				break;
			}
			case 1:{
				if(num<m){
					ans[t]=-1;
					continue;
				}
				int l=0,r=mx;
				while(l<r){
					int mid=(l+r)>>1;
					if(query(rt,1,mx,min(x+mid+1,mx),mx)<max(x-mid,1)){
						l=mid+1;
					}else{
						r=mid;
					}
				}
				ans[t]=l;
				break;
			}
			default:{
				if(vis[t]--==1){
					num--;
				}
				auto it=pos[t].find(x);
				erase(rt,1,mx,x,*prev(it));
				erase(rt,1,mx,*next(it),x);
				insert(rt,1,mx,*next(it),*prev(it));
				pos[t].erase(it);
				break;
			}
		}
	}
	for(int i=1;i<=q;i++){
		cout<<ans[i]<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}
posted @ 2025-08-10 19:45  zhangxy__hp  阅读(16)  评论(0)    收藏  举报