Living-Dream 系列笔记 第77期

Posted on 2024-09-11 21:37  _XOFqwq  阅读(6)  评论(0编辑  收藏  举报

拖更了一个暑假。

P6492

很妙的线段树阿。

对于修改,我们无需用 lazy tag,只要每次跑到叶子节点去直接修改即可。

对于询问,答案即为树根的信息,因为它每次询问的都是整个区间。

最难的是 pushup 部分:

我们需要维护三个东西,ans,lx,rx,分别表示当前节点的 整个串的最长合法串 / 左端点开头的最长合法串 / 右端点开头的最长合法串;

ans 可以取自 左孩子的 ans / 右孩子的 ans / 左孩子的 rx \(+\) 右孩子的 lx(必须满足连接处的字符不一样)。

lx 可以取自 左孩子的 lx \(+\) 右孩子的 lx(要加上后面部分必须满足左孩子的 lx 是整个左孩子区间且连接处的字符不一样)。

rx 可以取自 右孩子的 rx \(+\) 左孩子的 lx(要加上后面部分必须满足右孩子的 rx 是整个右孩子区间且连接处的字符不一样)。

总结:本题维护了线段树上的多个信息,且涉及到了跨区间合并信息,十分具有启发性。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e6+5;
int n,q;
int a[N];
struct SGT{
	int ans,lx,rx;
}tree[N];

void pushup(int p,int lt,int rt){
	int mid=(lt+rt)>>1;
	
	tree[p].lx=tree[p*2].lx;
	if(tree[p*2].lx==mid-lt+1&&a[mid]!=a[mid+1])
		tree[p].lx+=tree[p*2+1].lx;
	
	tree[p].rx=tree[p*2+1].rx;
	if(tree[p*2+1].rx==rt-mid&&a[mid]!=a[mid+1])
		tree[p].rx+=tree[p*2].rx;
	
	tree[p].ans=max(tree[p*2].ans,tree[p*2+1].ans);
	if(a[mid]!=a[mid+1])
		tree[p].ans=max(tree[p].ans,tree[p*2].rx+tree[p*2+1].lx); 
}
void bld(int p,int lt,int rt){
	if(lt==rt){
		tree[p].ans=tree[p].lx=tree[p].rx=1;
		return;
	}
	int mid=(lt+rt)>>1;
	bld(p*2,lt,mid);
	bld(p*2+1,mid+1,rt);
	pushup(p,lt,rt);
}
void upd(int p,int lt,int rt,int x){
	if(lt>x||rt<x)
		return;
	else if(lt==x&&rt==x){
		a[x]^=1;
		return;
	}
	
	int mid=(lt+rt)>>1;
	upd(p*2,lt,mid,x);
	upd(p*2+1,mid+1,rt,x);
	pushup(p,lt,rt);
}

int main(){
	ios::sync_with_stdio(0);
	cin>>n>>q;
	bld(1,1,n);
	while(q--){
		int x;
		cin>>x;
		upd(1,1,n,x);
		cout<<tree[1].ans<<'\n';
	}
	return 0;
}

P2894

和上一题很相似。

这题有区间修改,所以要 lazy tag(注意有三种情形:无标记、空房、非空房)。

pushup 同上,只是要去掉「满足连接处的字符不一样」这个条件(有个问题,为什么不要判连接处的两个字符都为 \(0\)?第一判不了,因为这题是区间修改;第二因为 ans,lx,rx 会在打 lazy tag 时进行重置,因此无需担心正确性问题)。

询问的时候就找 左孩子 / 右孩子 / 中间拼接 这三个地方是否有一个地方的答案大于等于 \(x\),哪里满足就去哪里,一个都没有就无解。

总结:与上题类似,略。

code
#include<bits/stdc++.h>
using namespace std;

const int N=2e5+5;
int n,q;
int a[N],tag[N];
struct SGT{
	int ans,lx,rx;
}tree[N];

void addtag(int p,int lt,int rt,int val){
	tag[p]=val;
	if(val==1)
		tree[p].ans=tree[p].lx=tree[p].rx=0;
	else
		tree[p].ans=tree[p].lx=tree[p].rx=rt-lt+1;
}
void pushdown(int p,int lt,int rt){
	if(!tag[p])
		return;
	int mid=(lt+rt)>>1;
	addtag(p*2,lt,mid,tag[p]);
	addtag(p*2+1,mid+1,rt,tag[p]);
	tag[p]=0;
}
void pushup(int p,int lt,int rt){
	int mid=(lt+rt)>>1;
	tree[p].lx=tree[p*2].lx;
	if(tree[p*2].lx==mid-lt+1)
		tree[p].lx+=tree[p*2+1].lx;
	tree[p].rx=tree[p*2+1].rx;
	if(tree[p*2+1].rx==rt-mid)
		tree[p].rx+=tree[p*2].rx;
	tree[p].ans=max({tree[p*2].ans,tree[p*2+1].ans,tree[p*2].rx+tree[p*2+1].lx});
}
void bld(int p,int lt,int rt){
	if(lt==rt){
		tree[p].ans=tree[p].lx=tree[p].rx=1;
		return;
	}
	int mid=(lt+rt)>>1;
	bld(p*2,lt,mid);
	bld(p*2+1,mid+1,rt);
	pushup(p,lt,rt);
}
void upd(int p,int lt,int rt,int ql,int qr,int val){
	if(lt>qr||rt<ql)
		return;
	else if(ql<=lt&&rt<=qr){
		addtag(p,lt,rt,val);
		return;
	}
	pushdown(p,lt,rt);
	int mid=(lt+rt)>>1;
	upd(p*2,lt,mid,ql,qr,val);
	upd(p*2+1,mid+1,rt,ql,qr,val);
	pushup(p,lt,rt);
}
int qry(int p,int lt,int rt,int x){
	if(lt==rt)
		return lt;
	pushdown(p,lt,rt);
	int mid=(lt+rt)>>1;
	if(tree[p*2].ans>=x)
		return qry(p*2,lt,mid,x);
	if(tree[p*2].rx+tree[p*2+1].lx>=x)
		return mid-tree[p*2].rx+1;
	if(tree[p*2+1].ans>=x)
		return qry(p*2+1,mid+1,rt,x);
	return 0;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>q;
	bld(1,1,n);
	while(q--){
		int op,x,y;
		cin>>op>>x;
		if(op==1){
			int pos=qry(1,1,n,x);
			if(pos!=0)
				upd(1,1,n,pos,pos+x-1,1);
			cout<<pos<<'\n';
		}
		else{
			cin>>y;
			upd(1,1,n,x,x+y-1,2);
		}
	}
	return 0;
}

CF620E

看到数颜色差点以为是莫队。。。

这题是维护子树信息,我们求出 dfs 序即可转化为序列问题。

然后它询问子树内节点的颜色种类数,我们考虑状压,将每个子树内的颜色情况压缩为一个二进制数(即有第 \(i\) 种颜色则第 \(i\)\(1\),否则为 \(0\)),然后每个节点的状态即为两个子节点的状态或起来的值,每次询问时回答当前区间状态中 \(1\) 的个数即可。

总结:本题运用了 dfs 序将树上问题转化为序列问题、以及二进制压缩的技巧,尤其是后者十分精妙,需要牢记,可以在数颜色这一类的题目中使用。