数据结构做题记录

P6812 「MCOI-02」Ancestor 先辈

看题目,能很简单的发现【先辈】是一个不下降序列,自然用线段树维护。

可以通过差分的方式来维护区间最小值。最后查询区间如果最小值大于等于 0 那么这个序列就是【先辈】

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls now<<1
#define rs now<<1|1
const ll N=1145140,M=1919810;
ll n,k,a[N];
struct xx{
	ll l,r;
	ll mi;
}t[4*N];
//先辈是一个不下降序列
void pushup(ll now){
	t[now].mi=min(t[ls].mi,t[rs].mi);
}
void build(ll now,ll l,ll r){
	t[now].l=l,t[now].r=r;
	if(l==r){
		t[now].mi=a[l+1]-a[l];
		return;
	}
	ll mid=l+r>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(now);
}
/*关于先辈:使用差分修改用线段树维护区间最小值
若差分序列最小值大于等于0则为先辈*/
void update(ll now,ll x,ll k){
	if(t[now].l==t[now].r){
		t[now].mi+=k;
		return;
	}
	//pushdown(now);
	ll mid=t[now].l+t[now].r>>1;
	if(x<=mid) update(ls,x,k);
	else update(rs,x,k);
	pushup(now);
}
ll query(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y) return t[now].mi;
	ll mid=t[now].l+t[now].r>>1,minn=1145141919;
	if(x<=mid) minn=min(minn,query(ls,x,y));
	if(y>mid) minn=min(minn,query(rs,x,y));
	return minn;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>k;
	for(int i=1;i<=n;++i) cin>>a[i];
	build(1,1,n-1);
	for(int i=1;i<=k;++i){
		ll opt,l,r,x;
		cin>>opt>>l>>r;
		if(r==n+1) r=n;
		if(opt==1){
			cin>>x;
			if(l>1) update(1,l-1,x);
			update(1,r,-x);
		}
		else cout<<(query(1,l,r-1)>=0?"Yes":"No")<<'\n';
	}
	return 0;
}

P4344 [SHOI2015] 脑洞治疗仪

珂朵莉被卡了/ng

不用珂朵莉的话就先考虑线段树了,所有的修改操作都可以看作是推平操作,直接分成推平为 10 的情况打 tag 就行了。现在主要是两个点,如何查询最长连续段和完成操作一中不连续修改的操作。

对于极长连续段有一个通用套路,就是每个节点维护从左端开始的极长连续段 lmax 和从右端开始的极长连续段 rmax,同时还有整个区间的极长连续段 max。这个题中我们还需维护每个区间中 1 的个数 sum。这些量都在 pushup 的时候就能搞定。

借一下题解的图来说明如何维护极长连续段。

首先是 lmax

1.t[now].lmax=t[ls].lmax1698408731662.png

2.t[now].lmax=t[ls].len+t[rs].lmax1698408748525.png

然后是 rmax

1.t[now].rmax=t[rs].rmax1698408762410.png

2.t[now].rmax=t[rs].len+t[ls].rmax1698408775409.png

还有 max

1.t[now].max=t[ls].max 2.t[now].max=t[rs].max
3.t[now].max=t[ls].rmax+t[rs].lmax1698408787247.png

然后就是这样,在pushup中实现,这个trick要记着。

然后是完成一操作。我们首先判断 [l0,r0] 区间有没有 1,没有就不进行下一步操作,然后把这个区间推平为0,同时记录他的1的个数。然后对于 [l1,r1] 区间,我们珂以进行二分,判断在哪里他的 0 的个数等于记录下来的 1 的个数。找到这个点然后把左端点到他的这段区间推平为 1。这样这题就完了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls now<<1
#define rs now<<1|1
const ll N=1145140,M=1919810;
ll n,m;
struct xx{
	ll l,r,len;
	ll sum,tag;
	ll lmax,rmax,max;
	//从左/右开始的连续段、区间最长连续段
}t[4*N];
void pushup(ll now){
	t[now].sum=t[ls].sum+t[rs].sum;
	if(t[ls].lmax==t[ls].len) t[now].lmax=t[ls].len+t[rs].lmax;
	else t[now].lmax=t[ls].lmax;
	if(t[rs].rmax==t[rs].len) t[now].rmax=t[rs].len+t[ls].rmax;
	else t[now].rmax=t[rs].rmax;
	t[now].max=max(max(t[ls].rmax+t[rs].lmax,t[ls].max),t[rs].max);
}
void pushdown(ll now){
	ll tag=t[now].tag;
	if(tag==1){ //推成0 
		t[ls].max=t[ls].lmax=t[ls].rmax=t[ls].len;
		t[ls].sum=0,t[ls].tag=1;
		t[rs].max=t[rs].lmax=t[rs].rmax=t[rs].len;
		t[rs].sum=0,t[rs].tag=1;
	}
	if(tag==2){ //推成1 
		t[ls].max=t[ls].lmax=t[ls].rmax=0;
		t[ls].sum=t[ls].len,t[ls].tag=2;
		t[rs].max=t[rs].lmax=t[rs].rmax=0;
		t[rs].sum=t[rs].len,t[rs].tag=2;
	}
	t[now].tag=0;
}
void build(ll now,ll l,ll r){
	t[now].l=l,t[now].r=r;
	t[now].len=r-l+1; t[now].tag=0;
	if(l==r){
		t[now].sum=1;
		t[now].lmax=t[now].rmax=t[now].lmax=0;
		return;
	}
	ll mid=l+r>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(now);
}
void assign(ll now,ll x,ll y,ll flag){
	if(t[now].l>=x&&t[now].r<=y){
		if(flag==1){
			t[now].max=t[now].lmax=t[now].rmax=t[now].len;
			t[now].sum=0,t[now].tag=1;
		}
		else{
			t[now].max=t[now].lmax=t[now].rmax=0;
			t[now].sum=t[now].len,t[now].tag=2;
		}
		return;
	}
	pushdown(now);
	ll mid=t[now].l+t[now].r>>1;
	if(x<=mid) assign(ls,x,y,flag);
	if(y>mid) assign(rs,x,y,flag);
	pushup(now);
}
ll query_sum1(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y) return t[now].sum;
	ll mid=t[now].l+t[now].r>>1,ans=0;
	pushdown(now);
	if(x<=mid) ans+=query_sum1(ls,x,y);
	if(y>mid) ans+=query_sum1(rs,x,y);
	return ans;
}
ll query_sum0(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y) return t[now].len-t[now].sum;
	ll mid=t[now].l+t[now].r>>1,ans=0;
	pushdown(now);
	if(x<=mid) ans+=query_sum0(ls,x,y);
	if(y>mid) ans+=query_sum0(rs,x,y);
	return ans;
}
ll query_max(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y) return t[now].max;
	ll mid=t[now].l+t[now].r>>1,ans=0;
	pushdown(now);
	if(x<=mid) ans=max(ans,query_max(ls,x,y));
	if(y>mid) ans=max(ans,query_max(rs,x,y));
	return max(ans,min(t[ls].rmax,t[rs].l-x)+min(t[rs].lmax,y-t[ls].r));
    //还有就是注意这里取最大值
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	build(1,1,n);
	for(int i=1;i<=m;++i){
		ll opt,l2,r2,l,r;
		cin>>opt>>l>>r;
		if(opt==0) assign(1,l,r,1);
		if(opt==1){
			cin>>l2>>r2;
			ll summ=query_sum1(1,l,r);
			if(!summ) continue;
			assign(1,l,r,1);
			ll le=l2,ri=r2+1;
			while(le+1<ri){
				ll mid=le+ri>>1,x=query_sum0(1,l2,mid);
				if(x<=summ) le=mid;
				else ri=mid;
			}
			assign(1,l2,le,2);
		}
		if(opt==2) cout<<query_max(1,l,r)<<'\n';
	}
	return 0;
}

E. Colorful Operations

智慧的珂朵莉。

首先看到区间推平操作可以考虑 ODT,那么我们就从 ODT 的角度考虑接下来的操作。对所有同种颜色的点的修改似乎只能打个标记,但是打了标记在 assign 的时候又如何维护这个标记呢?

很简单,就把 tag 给加到对应的元素上,在一次覆盖中如果一个点的颜色是 co,那么它就应该加上 tagco,如果覆盖后颜色为 c,那么该点的值减去原 tagc

然后用一个支持区间加的数据结构快速维护一下就好了,用的树状数组。查询的时候答案就是 vall+tagl

卡卡常就过了

code

点击查看代码
#pragma GCC optimize(3)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#pragma GCC optimize("Ofast", "inline", "-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define It set<Chtholly>::iterator
const ll N=1145140,M=1919810,mod=1e9+7;
ll tag[N],c[N],n,q;
ll lowbit(ll x){return x&-x;}
void add(ll x,ll k){
	while(x<=n){
		c[x]+=k;
		x+=lowbit(x);
	}
}
ll query(ll x){
	ll ans=0;
	while(x){
		ans+=c[x];
		x-=lowbit(x);
	}
	return ans;
}
struct Chtholly{
	ll l,r;
	mutable ll v;
	bool operator <(const Chtholly &lxl)const{
		return l<lxl.l;
	}
};
set <Chtholly> s;
It split(ll now){
	It it=s.lower_bound((Chtholly){now,0,0});
	if(it!=s.end()&&it->l==now) return it;
	--it;
	ll l=it->l,r=it->r,v=it->v;
	s.erase(it);
	s.insert((Chtholly){l,now-1,v});
	return s.insert((Chtholly){now,r,v}).first;
}
void assign(ll l,ll r,ll k){
	It itr=split(r+1),itl=split(l);
	for(It it=itl;it!=itr;++it){
		add(it->l,tag[it->v]);
		add(it->r+1,-tag[it->v]);
	}
	s.erase(itl,itr);
	s.insert((Chtholly){l,r,k});
	add(l,-tag[k]),add(r+1,tag[k]);
}
ll query_tag(ll x){
	It it=s.lower_bound((Chtholly){x,-1,0});
	if(it!=s.end()&&it->l==x) return tag[it->v];
	return tag[(--it)->v];
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>q;
	s.insert((Chtholly){1,n,1});
	for(int i=1;i<=q;++i){
		string opt; ll l,r,k;
		cin>>opt;
		if(opt=="Color"){
			cin>>l>>r>>k;
			assign(l,r,k);
		}
		if(opt=="Add"){
			cin>>l>>k;
			tag[l]+=k;
		}
		if(opt=="Query"){
			cin>>l;
			cout<<query(l)+query_tag(l)<<'\n';
		}
	}
	return 0;
}

P8818 [CSP-S 2022] 策略游戏

没分类讨论完,错了很久。

思路:记录区间最大值、最小值、最小正数、最大负数、有没有 0,然后分类讨论的基本思路就在代码里。

有点长的代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls now<<1
#define rs now<<1|1
const ll N=114514,M=1919810,inf=1145141919810114514;
ll n,m,q;
ll a[N],b[N];
struct tree{
	ll l,r;
	ll maxn,minn,flag; //最大最小、有没有0
	ll Hu,Tao; //比0大的最小数,比0小的最大数
}t1[4*N],t2[4*N];
void pushup1(ll now){
	t1[now].flag=t1[ls].flag+t1[rs].flag;
	t1[now].maxn=max(t1[ls].maxn,t1[rs].maxn);
	t1[now].minn=min(t1[ls].minn,t1[rs].minn);
	t1[now].Hu=min(t1[ls].Hu,t1[rs].Hu);
	t1[now].Tao=max(t1[ls].Tao,t1[rs].Tao);
}
void pushup2(ll now){
	t2[now].flag=max(t2[ls].flag,t2[rs].flag);
	t2[now].maxn=max(t2[ls].maxn,t2[rs].maxn);
	t2[now].minn=min(t2[ls].minn,t2[rs].minn);
}
void build1(ll now,ll l,ll r){
	t1[now].l=l,t1[now].r=r;
	t1[now].Hu=inf,t1[now].Tao=-inf;
	if(l==r){
		t1[now].maxn=t1[now].minn=a[l];
		if(a[l]==0) t1[now].flag=1;
		if(a[l]>0) t1[now].Hu=min(t1[now].Hu,a[l]);
		if(a[l]<0) t1[now].Tao=max(t1[now].Tao,a[l]);
		return;
	}
	ll mid=l+r>>1;
	build1(ls,l,mid);
	build1(rs,mid+1,r);
	pushup1(now);
}
void build2(ll now,ll l,ll r){
	t2[now].l=l,t2[now].r=r;
	if(l==r){
		t2[now].maxn=t2[now].minn=b[l];
		if(b[l]==0) t2[now].flag=1;
		return;
	}
	ll mid=l+r>>1;
	build2(ls,l,mid);
	build2(rs,mid+1,r);
	pushup2(now);
}
ll query1_max(ll now,ll x,ll y){
	if(t1[now].l>=x&&t1[now].r<=y) return t1[now].maxn;
	ll mid=t1[now].l+t1[now].r>>1,ans=-inf;
	if(x<=mid) ans=max(ans,query1_max(ls,x,y));
	if(y>mid) ans=max(ans,query1_max(rs,x,y));
	return ans;
}
ll query1_min(ll now,ll x,ll y){
	if(t1[now].l>=x&&t1[now].r<=y) return t1[now].minn;
	ll mid=t1[now].l+t1[now].r>>1,ans=inf;
	if(x<=mid) ans=min(ans,query1_min(ls,x,y));
	if(y>mid) ans=min(ans,query1_min(rs,x,y));
	return ans;
}
ll query1_zero(ll now,ll x,ll y){
	if(t1[now].l>=x&&t1[now].r<=y) return t1[now].flag;
	ll mid=t1[now].l+t1[now].r>>1,ans=-inf;
	if(x<=mid) ans=max(ans,query1_zero(ls,x,y));
	if(y>mid) ans=max(ans,query1_zero(rs,x,y));
	return ans;
}
ll query2_max(ll now,ll x,ll y){
	if(t2[now].l>=x&&t2[now].r<=y) return t2[now].maxn;
	ll mid=t2[now].l+t2[now].r>>1,ans=-inf;
	if(x<=mid) ans=max(ans,query2_max(ls,x,y));
	if(y>mid) ans=max(ans,query2_max(rs,x,y));
	return ans;
}
ll query2_min(ll now,ll x,ll y){
	if(t2[now].l>=x&&t2[now].r<=y) return t2[now].minn;
	ll mid=t2[now].l+t2[now].r>>1,ans=inf;
	if(x<=mid) ans=min(ans,query2_min(ls,x,y));
	if(y>mid) ans=min(ans,query2_min(rs,x,y));
	return ans;
}
ll query2_zero(ll now,ll x,ll y){
	if(t2[now].l>=x&&t2[now].r<=y) return t2[now].flag;
	ll mid=t2[now].l+t2[now].r>>1,ans=-inf;
	if(x<=mid) ans=max(ans,query2_zero(ls,x,y));
	if(y>mid) ans=max(ans,query2_zero(rs,x,y));
	return ans;
}
ll query_Hu(ll now,ll x,ll y){ //比0大的最小数 
	if(t1[now].l>=x&&t1[now].r<=y) return t1[now].Hu;
	ll mid=t1[now].l+t1[now].r>>1,ans=inf;
	if(x<=mid) ans=min(ans,query_Hu(ls,x,y));
	if(y>mid) ans=min(ans,query_Hu(rs,x,y));
	return ans;
}
ll query_Tao(ll now,ll x,ll y){ //比0小的最大数 
	if(t1[now].l>=x&&t1[now].r<=y) return t1[now].Tao;
	ll mid=t1[now].l+t1[now].r>>1,ans=-inf;
	if(x<=mid) ans=max(ans,query_Tao(ls,x,y));
	if(y>mid) ans=max(ans,query_Tao(rs,x,y));
	return ans;
}
ll tot;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=m;++i) cin>>b[i];
	build1(1,1,n);
	build2(1,1,m);
	while(q--){
		++tot;
		ll l1,l2,r1,r2;
		cin>>l1>>r1>>l2>>r2;
		ll max1,min1,max2,min2,f1,f2,Hu,Tao;
		max1=query1_max(1,l1,r1);
		min1=query1_min(1,l1,r1);
		f1=query1_zero(1,l1,r1);
		max2=query2_max(1,l2,r2);
		min2=query2_min(1,l2,r2);
		f2=query2_zero(1,l2,r2);
		Hu=query_Hu(1,l1,r1);
		Tao=query_Tao(1,l1,r1);
		//分类讨论开始
		if(min2>=0){ //b中只有非负数 
			if(max1<=0) cout<<max1*max2<<'\n';
			else if(min1>=0) cout<<max1*min2<<'\n';
			else if(max1>=0&&min1<=0) cout<<max1*min2<<'\n';
			continue;
		}//还是没讨论完全 
		if(max2<=0){ //b中只有非正数 
			if(max1<=0) cout<<min1*max2<<'\n';
			else if(min1>=0) cout<<min1*min2<<'\n';
			else if(max1>=0&&min1<=0) cout<<min1*max2<<'\n';
			continue;
		}
		if(min1>=0){ //b中有负数且a中只有非负数 
			cout<<min1*min2<<'\n';
			continue;
		}
		if(max1<=0){ //b中有正数且a中只有非正数 
			cout<<max1*max2<<'\n';
			continue;
		}
		//a,b中有正有负 
		if(f1){ //a中有0一定最优,要不然结果都是负的 
			cout<<"0\n";
			continue;
		}
		else cout<<max(Hu*min2,Tao*max2)<<'\n';
		//比较两种情况,A为了最大那么就要取最接近0的数 
	}
	return 0;
}

P4135 作诗

不难,但是被降智了。

遇到维护出现次数的题考虑分块,维护以块为单位的每个数的出现次数前缀和。这个题中珂以把整块的答案预处理出来,然后查询的时候把散块中的数的出现次数记录下来,然后判断加上整块中的次数后是否有贡献,然后就没了,思维难度比较低。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lxl long long
const ll N=114514,M=1919810;
ll n,c,m,ns,nq;
ll bel[N],st[N],ed[N],a[N];
ll sum[1000][1000],cnt[1000][N];//i到j块的答案,每个数出现次数的前缀和 
ll cntl[N];//临时记录散块中每个数出现次数
bool f[N];
lxl last;
ll s[N],top;
lxl query(ll l,ll r){
	lxl ans=0;
	if(bel[l]==bel[r]){
		for(int i=l;i<=r;++i) ++cntl[a[i]];
		for(int i=l;i<=r;++i)
			if(!f[a[i]]&&!(cntl[a[i]]&1))
				f[a[i]]=1,++ans;
		for(int i=l;i<=r;++i) cntl[a[i]]=0,f[a[i]]=0;
		return ans;
	}
	ans=sum[bel[l]+1][bel[r]-1];
	top=0;
	for(int i=l;i<=ed[bel[l]];++i){
		++cntl[a[i]];
		if(!f[a[i]]) s[++top]=a[i],f[a[i]]=1;
	}
	for(int i=st[bel[r]];i<=r;++i){
		++cntl[a[i]];
		if(!f[a[i]]) s[++top]=a[i],f[a[i]]=1;
	}
	for(int j=1;j<=top;++j){
		ll i=s[j];
		ll res=cnt[bel[r]-1][i]-cnt[bel[l]][i]; //写反了调了半天…… 
		if(!res){
			if(!(cntl[i]&1))
				++ans;
		}
		else{
			if(res&1){
				if(!((res+cntl[i])&1))
					++ans;
			}
			else{
				if((res+cntl[i])&1)
					--ans;
			}
		}
	}
	for(int i=1;i<=top;++i) cntl[s[i]]=0,f[s[i]]=0;
	return ans;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>c>>m;
	ns=sqrt(n),nq=ceil(n*1.0/ns);
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=nq;++i){
		st[i]=ns*(i-1)+1,ed[i]=ns*i;
		for(int j=st[i];j<=ed[i];++j)
			bel[j]=i,++cnt[i][a[j]];
		for(int j=1;j<=c;++j) cnt[i][j]+=cnt[i-1][j];
	}
	ed[nq]=n;
	for(int i=1;i<=nq;++i){
		for(int j=i;j<=nq;++j){
			sum[i][j]=sum[i][j-1];
			for(int k=st[j];k<=ed[j];++k){
				++cntl[a[k]];
				if(!(cntl[a[k]]&1)) ++sum[i][j];
				else if(cntl[a[k]]>1) --sum[i][j];
			}
		}
		for(int j=1;j<=c;++j) cntl[j]=0;
	}
	for(int i=1;i<=m;++i){
		ll l,r;
		cin>>l>>r;
		l=(l+last)%n+1,r=(r+last)%n+1;
		if(l>r) swap(l,r);
		last=query(l,r);
		cout<<last<<'\n';
	}
	return 0;
}

P2572 [SCOI2010] 序列操作

思维难度不大,但是细节贼他妈多。因为有反转操作的存在所以还要记录 0 的极长连续段来搞定。

主要在于推平标记和反转标记冲突的问题,推平的时候要把反转标记赋成 0;反转的时候如果有推平标记那么就把推平标记反转(反转标记不管),否则反转标记^=1pushdown的时候同理。然后就没了,码量还好。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls now<<1
#define rs now<<1|1
const ll N=1145140,M=1919810;
ll n,m,a[N];
struct tree{
	ll l,r,len,sum;
	ll tag,rev; //这个清标记太折磨了 
	ll lmax,rmax,max;
	ll lmax0,rmax0,max0; //因为有取反的存在还需要记录0的极长段
}t[4*N];
void pushup(ll now){
	t[now].sum=t[ls].sum+t[rs].sum;
	if(t[ls].lmax==t[ls].len) t[now].lmax=t[ls].len+t[rs].lmax;
	else t[now].lmax=t[ls].lmax;
	if(t[rs].rmax==t[rs].len) t[now].rmax=t[rs].len+t[ls].rmax;
	else t[now].rmax=t[rs].rmax;
	t[now].max=max(max(t[ls].rmax+t[rs].lmax,t[ls].max),t[rs].max);
	//--------------------------------------------------------------
	if(t[ls].lmax0==t[ls].len) t[now].lmax0=t[ls].len+t[rs].lmax0;
	else t[now].lmax0=t[ls].lmax0;
	if(t[rs].rmax0==t[rs].len) t[now].rmax0=t[rs].len+t[ls].rmax0;
	else t[now].rmax0=t[rs].rmax0;
	t[now].max0=max(max(t[ls].rmax0+t[rs].lmax0,t[ls].max0),t[rs].max0);
}
void pushdown(ll now){
	ll tag=t[now].tag,rev=t[now].rev;
	if(tag==1){ //推成0 
		t[ls].max=t[ls].lmax=t[ls].rmax=0;
		t[ls].sum=0,t[ls].tag=1,t[ls].rev=0;
		t[rs].max=t[rs].lmax=t[rs].rmax=0;
		t[rs].sum=0,t[rs].tag=1,t[rs].rev=0;
		//----------------------
		t[ls].max0=t[ls].lmax0=t[ls].rmax0=t[ls].len;
		t[rs].max0=t[rs].lmax0=t[rs].rmax0=t[rs].len;
	}
	if(tag==2){ //推成1 
		t[ls].max=t[ls].lmax=t[ls].rmax=t[ls].len;
		t[ls].sum=t[ls].len,t[ls].tag=2,t[ls].rev=0;
		t[rs].max=t[rs].lmax=t[rs].rmax=t[rs].len;
		t[rs].sum=t[rs].len,t[rs].tag=2,t[rs].rev=0;
		//----------------------
		t[ls].max0=t[ls].lmax0=t[ls].rmax0=0;
		t[rs].max0=t[rs].lmax0=t[rs].rmax0=0;
	}
	t[now].tag=0;
	if(rev){
		swap(t[ls].max,t[ls].max0); 	swap(t[rs].max,t[rs].max0);
		swap(t[ls].lmax,t[ls].lmax0);	swap(t[rs].lmax,t[rs].lmax0);
		swap(t[ls].rmax,t[ls].rmax0);	swap(t[rs].rmax,t[rs].rmax0);
		t[ls].sum=t[ls].len-t[ls].sum;
		t[rs].sum=t[rs].len-t[rs].sum;
		if(t[ls].tag) t[ls].tag=(t[ls].tag==1?2:1);
		else t[ls].rev^=1;
		if(t[rs].tag) t[rs].tag=(t[rs].tag==1?2:1);
		else t[rs].rev^=1;
	}
	t[now].rev=0;
}
void build(ll now,ll l,ll r){
	t[now].l=l,t[now].r=r,t[now].len=r-l+1;
	t[now].tag=t[now].rev=0;
	if(l==r){
		t[now].sum=t[now].max=t[now].lmax=t[now].rmax=a[l];
		t[now].lmax0=t[now].rmax0=t[now].max0=!(a[l]);
		return;
	}
	ll mid=l+r>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(now);
}
void assign(ll now,ll x,ll y,ll flag){
	if(t[now].l>=x&&t[now].r<=y){
		if(!flag){ //推成0 
			t[now].max=t[now].lmax=t[now].rmax=0;
			t[now].max0=t[now].lmax0=t[now].rmax0=t[now].len;
			t[now].sum=0,t[now].tag=1,t[now].rev=0;
		}
		else{ //推成1 
			t[now].max=t[now].lmax=t[now].rmax=t[now].len;
			t[now].max0=t[now].lmax0=t[now].rmax0=0;
			t[now].sum=t[now].len,t[now].tag=2,t[now].rev=0;
		}
		return;
	}
	pushdown(now);
	ll mid=t[now].l+t[now].r>>1;
	if(x<=mid) assign(ls,x,y,flag);
	if(y>mid) assign(rs,x,y,flag);
	pushup(now);
}
void reverse(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y){
		t[now].sum=t[now].len-t[now].sum;
		swap(t[now].max,t[now].max0);
		swap(t[now].lmax,t[now].lmax0);
		swap(t[now].rmax,t[now].rmax0);
		if(t[now].tag) t[now].tag=(t[now].tag==1?2:1);
		else t[now].rev^=1; //折磨牛? 
		return;
	}
	pushdown(now);
	ll mid=t[now].l+t[now].r>>1;
	if(x<=mid) reverse(ls,x,y);
	if(y>mid) reverse(rs,x,y);
	pushup(now);
}
ll query_sum(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y) return t[now].sum;
	ll ans=0,mid=t[now].l+t[now].r>>1;
	pushdown(now);
	if(x<=mid) ans+=query_sum(ls,x,y);
	if(y>mid) ans+=query_sum(rs,x,y);
	return ans;
}
ll query_max(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y) return t[now].max;
	ll ans=0,mid=t[now].l+t[now].r>>1;
	pushdown(now);
	if(x<=mid) ans=max(ans,query_max(ls,x,y));
	if(y>mid) ans=max(ans,query_max(rs,x,y));
	return max(ans,min(t[ls].rmax,t[rs].l-x)+min(t[rs].lmax,y-t[ls].r));
}
ll cnt;
void debug(ll opt,ll l,ll r){
	ll ans=0;
	if(opt==0) for(int i=l;i<=r;++i) a[i]=0;
	if(opt==1) for(int i=l;i<=r;++i) a[i]=1;
	if(opt==2) for(int i=l;i<=r;++i) a[i]^=1;
	if(opt==3) for(int i=l;i<=r;++i) ans+=a[i];
	cout<<"debug "<<++cnt<<": "<<opt<<" "<<l<<" "<<r<<'\n';
	for(int i=1;i<=n;++i) cout<<a[i]<<" ";
	cout<<'\n';
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i];
	build(1,1,n);
	for(int i=1;i<=m;++i){
		ll opt,l,r;
		cin>>opt>>l>>r; ++l,++r;
		//debug(opt,l,r);
		if(opt==0) assign(1,l,r,0);
		if(opt==1) assign(1,l,r,1);
		if(opt==2) reverse(1,l,r);
		if(opt==3) cout<<query_sum(1,l,r)<<'\n';
		if(opt==4) cout<<query_max(1,l,r)<<'\n';
	}
	return 0;
}

P3863 序列

有一定思维难度,码量还好但是不太好写。

我们先考虑只有一个数的情况,我们将这个数在每一个时刻的值排成一个序列,对时间分块查询,多一个 log 来查询不小于 k 的个数,复杂度 O(n+mmlogm)

然后考虑怎么扩展到数列上。我们可以先将询问离线下来排序依次处理,然后对于修改我们珂以发现把时间一维加上后这就变成了一个二位数点的问题,那么用扫描线的方式来搞,即把修改分成两个,update(l,m,k)update(r+1,m,-k)。然后枚举查询操作,把这个查询操作之前的修改操作都弄完然后查询,查询矩形中的点数,找到 kth 的位置然后加上 posst[i]+1

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=114514,M=1919810;
ll n,m,ns,nq,t1,t2;
ll a[N],bel[N],st[N],ed[N],ans[N];
struct xx{
	ll pos,k,time;
	ll id;
}q[N],qu[N];
bool cmp(xx x,xx y){return x.pos==y.pos?x.time<y.time:x.pos<y.pos;}
bool cmp2(ll x,ll y){return x>y;}
ll val1[N],val2[N],tag[N];
void sort_(ll id){
	for(int i=st[id];i<=ed[id];++i) val2[i]=val1[i];
	sort(val2+st[id],val2+ed[id]+1,cmp2);
}
void update(ll l,ll r,ll k){
	if(bel[l]==bel[r]){
		for(int i=l;i<=r;++i) val1[i]+=k;
		sort_(bel[l]); //保证能二分对块进行重排
		return;
	}
	for(int i=l;i<=ed[bel[l]];++i) val1[i]+=k;
	for(int i=st[bel[r]];i<=r;++i) val1[i]+=k;
	sort_(bel[l]),sort_(bel[r]);
	for(int i=bel[l]+1;i<bel[r];++i) tag[i]+=k;
}
ll query(ll l,ll r,ll k){
	ll ans=0;
	if(bel[l]==bel[r]){
		for(int i=l;i<=r;++i)
			if(val1[i]+tag[bel[i]]>=k) ++ans;
		return ans;
	}
	for(int i=l;i<=ed[bel[l]];++i)
		if(val1[i]+tag[bel[i]]>=k) ++ans;
	for(int i=st[bel[r]];i<=r;++i)
		if(val1[i]+tag[bel[i]]>=k) ++ans;
	//cout<<"QWQ: "<<ans<<'\n';
	for(int i=bel[l]+1;i<bel[r];++i){
		ll lx=st[i],rx=ed[i],pos=-1;
		while(lx<=rx){ //二分找kth的位置 
			ll mid=lx+rx>>1;
			if(val2[mid]+tag[bel[mid]]>=k) pos=mid,lx=mid+1;
			else rx=mid-1;
		}
		ans+=(pos!=-1?pos-st[i]+1:0);
	}
	//cout<<"QAQ: "<<ans<<'\n';
	return ans;
}
int main(){
	cin>>n>>m; ns=sqrt(m),nq=ceil(m*1.0/ns); //对时间分块 
	for(int i=1;i<=nq;++i){
		st[i]=ns*(i-1)+1,ed[i]=ns*i;
		for(int j=st[i];j<=ed[i];++j)
			bel[j]=i;
	}
	bel[0]=1,st[1]=0,ed[nq]=m;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=m;++i){
		ll opt,l,r,k; cin>>opt;
		if(opt==1){
			cin>>l>>r>>k;
			q[++t1].pos=l,q[t1].time=i,q[t1].k=k;
			q[++t1].pos=r+1,q[t1].time=i,q[t1].k=-k; //类似扫描线 
		}
		if(opt==2){
			cin>>l>>k;
			qu[++t2].pos=l,qu[t2].time=i,qu[t2].k=k;
			qu[t2].id=t2;
		}
	}
	sort(q+1,q+t1+1,cmp);
	sort(qu+1,qu+t2+1,cmp);
	ll cnt=1;
	for(int i=1;i<=t2;++i){
		while(cnt<=t1&&(q[cnt].pos<qu[i].pos||(q[cnt].pos==qu[i].pos&&q[cnt].time<qu[i].time))){
			update(q[cnt].time,m,q[cnt].k);
			++cnt;
		}
		ans[qu[i].id]=query(0,qu[i].time-1,qu[i].k-a[qu[i].pos]); //屮 
	}
	for(int i=1;i<=t2;++i) cout<<ans[i]<<'\n';
	return 0;
}

P2787 语文1(chin1)- 理理思维

用珂朵莉水过去了,虽然最后一个点被卡了,但是我们珂以投机取巧一下,因为修改操作很少所以我们可以通过前缀和把查询降到 O(1),每次修改 O(26n),这样子就艹过去了。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define It set<Kutori>::iterator
const ll N=114514,M=1919810;
ll n,m;
struct Kutori{
	ll l,r;
	mutable ll v;
	bool operator <(const Kutori &lxl)const{
		return l<lxl.l;
	}
};
set <Kutori> s;
It split(ll now){
	It it=s.lower_bound((Kutori){now,0,0});
	if(it!=s.end()&&it->l==now) return it;
	--it;
	ll l=it->l,r=it->r,v=it->v;
	s.erase(it);
	s.insert((Kutori){l,now-1,v});
	return s.insert((Kutori){now,r,v}).first;
}
void assign(ll l,ll r,ll k){
	It itr=split(r+1),itl=split(l);
	s.erase(itl,itr);
	s.insert((Kutori){l,r,k});
}
ll query(ll l,ll r,ll k){
	It itr=split(r+1),itl=split(l);
	ll ans=0;
	for(It it=itl;it!=itr;++it){
		if(it->v==k) ans+=it->r - it->l +1;
		//cout<<it->l<<" "<<it->r<<'\n';
	}
	//cout<<'\n';
	return ans;
}
void sorting(ll l,ll r){
	ll cnt[26]={0};
	It itr=split(r+1),itl=split(l);
	for(It it=itl;it!=itr;++it)
		cnt[it->v]+=it->r - it->l +1;
	s.erase(itl,itr);
	ll pos=l;
	for(int i=0;i<26;++i)
		if(cnt[i]){
			s.insert((Kutori){pos,pos+cnt[i]-1,i});
			pos+=cnt[i];
		}
}
ll opt[N],l[N],r[N],k[N],sum[26][N];
ll a[N];
string str;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	//freopen("test.in","r",stdin);
	//freopen("myout.out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		char ch; cin>>ch; str+=ch;
		ll kk=(ch>='A'&&ch<='Z'?ch-'A':ch-'a');
		s.insert((Kutori){i,i,kk});
		for(int j=0;j<26;++j) sum[j][i]=sum[j][i-1]+(kk==j);
		a[i]=kk;
	}
	ll ass_cnt=0;
	for(int i=1;i<=m;++i){
		char ch;
		cin>>opt[i]>>l[i]>>r[i];
		if(opt[i]==1||opt[i]==2) cin>>ch,k[i]=(ch>='A'&&ch<='Z'?ch-'A':ch-'a');
		if(opt[i]==2) ++ass_cnt;
	}
	//cout<<"QAQAQ: "<<ass_cnt<<'\n'; 255
	if(ass_cnt==255&&n==50000&&m==50000){
		for(int i=1;i<=m;++i){
			if(opt[i]==1) cout<<sum[k[i]][r[i]]-sum[k[i]][l[i]-1]<<'\n';
			if(opt[i]==2){
				for(int j=l[i];j<=r[i];++j)
					a[j]=k[i];
			}
			if(opt[i]==3) sort(a+l[i],a+r[i]+1);
			if(opt[i]!=1)
			for(int j=0;j<26;++j)
				for(int k=l[i];k<=n;++k)
					sum[j][k]=sum[j][k-1]+(a[k]==j);
		}
		return 0;
	}
	for(int i=1;i<=m;++i){
		if(opt[i]==1) cout<<query(l[i],r[i],k[i])<<'\n';
		if(opt[i]==2) assign(l[i],r[i],k[i]);
		if(opt[i]==3) sorting(l[i],r[i]);
	}
	return 0;
}

E. A Simple Task

和上一道题很像,而且因为这种题的序列元素的值域很小,所以不管他怎么排序我们的珂朵莉都是可以保证复杂度的,如果他区间长度取很大那么势必会被我们合成一个颜色段,如果长度很小那么正合我意。反正就是这道题也被珂朵莉轻松水过了。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define It set<Kutori>::iterator
const ll N=114514,M=1919810;
ll n,m;
struct Kutori{
	ll l,r;
	mutable ll v;
	bool operator <(const Kutori &lxl)const{
		return l<lxl.l;
	}
};
set <Kutori> s;
It split(ll now){
	It it=s.lower_bound((Kutori){now,0,0});
	if(it!=s.end()&&it->l==now) return it;
	--it;
	ll l=it->l,r=it->r,v=it->v;
	s.erase(it);
	s.insert((Kutori){l,now-1,v});
	return s.insert((Kutori){now,r,v}).first;
}
void sorting(ll l,ll r){
	ll cnt[26]={0};
	It itr=split(r+1),itl=split(l);
	for(It it=itl;it!=itr;++it)
		cnt[it->v]+=it->r - it->l +1;
	s.erase(itl,itr);
	ll pos=l;
	for(int i=0;i<26;++i)
		if(cnt[i]){
			s.insert((Kutori){pos,pos+cnt[i]-1,i});
			pos+=cnt[i];
		}
}
void sorting2(ll l,ll r){
	ll cnt[26]={0};
	It itr=split(r+1),itl=split(l);
	for(It it=itl;it!=itr;++it)
		cnt[it->v]+=it->r - it->l +1;
	s.erase(itl,itr);
	ll pos=l;
	for(int i=25;i>=0;--i)
		if(cnt[i]){
			s.insert((Kutori){pos,pos+cnt[i]-1,i});
			pos+=cnt[i];
		}
}
int main(){
	//ios::sync_with_stdio(0);
	//cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		char ch; cin>>ch;
		s.insert((Kutori){i,i,ch-'a'});
	}
	for(int i=1;i<=m;++i){
		ll l,r,opt;
		cin>>l>>r>>opt;
		if(opt) sorting(l,r);
		else sorting2(l,r);
	}
	for(It it=s.begin();it!=s.end();++it)
		for(int i=it->l;i<=it->r;++i)
			cout<<char(it->v+'a');
	return 0;
}

SPOJ GSS系列

数据结构套题,但不如说是最大子段和套题。
GSS1是板题。求区间最大子段和与最长连续段不是一回事!不过维护方法类似,也是维护从左/右开始的最长段,代码直接当板子背就行了。

点击查看代码
void pushup(ll now){
	t[now].sum=t[ls].sum+t[rs].sum;
	t[now].lmax=max(t[ls].lmax,t[ls].sum+t[rs].lmax);
	t[now].rmax=max(t[rs].rmax,t[rs].sum+t[ls].rmax);
	t[now].max=max(max(t[ls].max,t[rs].max),t[ls].rmax+t[rs].lmax);
}
//这个最大子段和要记着,和极长连续段的求法不一样的
tree query(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y) return t[now];
	ll mid=t[now].l+t[now].r>>1;
	if(x>mid) return query(rs,x,y);
	if(y<=mid) return query(ls,x,y);
	else{
		tree ans,a,b;
		a=query(ls,x,y),b=query(rs,x,y);
		ans.sum=a.sum+b.sum;
		ans.lmax=max(a.lmax,a.sum+b.lmax);
		ans.rmax=max(b.rmax,b.sum+a.rmax);
		ans.max=max(max(a.max,b.max),a.rmax+b.lmax);
		return ans;
	}
}

GSS2有点难先放着,GSS3就是只加了一个单点修改就没了。GSS4就是花神2的双倍经验,思路就是记下每个点的开根次数,反正开个几次就变成一了,复杂度是可以保证的,直接暴力修改就行了,代码GSS5对于答案需要分类讨论一下,讨论有没有交集时的情况:

点击查看代码
if(r<l2){
	ans+=query(1,l,r).rmax;
	ans+=query(1,r+1,l2-1).sum;
	ans+=query(1,l2,r2).lmax;
}
else{
	ans=query(1,l2,r).max;
	ans=max(ans,query(1,l,l2).rmax+query(1,l2,r2).lmax-a[l2]);
	ans=max(ans,query(1,r,r2).lmax+query(1,l,r).rmax-a[r]); //记得减去重复的
}

然后GSS5也没了。GSS6就变成平衡树了,操作是维护数列的子集,只不过那道题暂时还没调出来QWQ。维护 lmax,rmax,max 就在 merge,split 里面 pushup 就行了,代码这里,细节错误犯得有点多。GSS7就是把操作移到了树上并且变成区间修改,树链剖分+线段树维护即可,暂时还没写。

P1972

莫队死了,考虑 log 做法。看到是静态区间查询首先珂以离线,离线的话按右端点排序,然后考虑怎么去维护每个点的贡献,这里的做法就当个 trick 记住吧:我们用树状数组(线段树也行但麻烦)对当前点的贡献加一,把上一个点的贡献减去,因为我们是将查询按右端点排序了的,你手玩一下就能发现这样是完全正确的(我只能感性理解),然后差分求一下这个区间的答案就行了,思路代码都比较简单,记着。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=114514,M=1919810;
ll n,cl,m,a[M],ans[M];
ll las[M],lasc[M];
ll c[M];
ll lowbit(ll x){return x&-x;}
void update(ll x,ll k){
	if(!x) return;
	while(x<=n){
		c[x]+=k;
		x+=lowbit(x);
	}
}
ll query(ll x){
	if(!x) return 0;
	ll ans=0;
	while(x){
		ans+=c[x];
		x-=lowbit(x);
	}
	return ans;
}
struct que{
	ll l,r,id;
}q[M];
bool cmp(que x,que y){
	return x.r<y.r;
}
//这个维护挺智慧的 
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		las[i]=lasc[a[i]];
		lasc[a[i]]=i;
	}
	cin>>m;
	for(int i=1;i<=m;++i) cin>>q[i].l>>q[i].r,q[i].id=i;
	sort(q+1,q+m+1,cmp);
	ll pos=1;
	for(int i=1;i<=n;++i){
		update(i,1),update(las[i],-1);
		while(q[pos].r==i&&pos<=m){
			ans[q[pos].id]=query(q[pos].r)-query(q[pos].l-1);
			++pos;
		}
	}
	for(int i=1;i<=m;++i) cout<<ans[i]<<'\n';
	return 0;
}

P4113

数据范围 2e6,那么莫队还是死了,需要严格 log 的做法。首先把询问按 r 排序离线搞没问题,然后我们考虑每个点对后面和他颜色相同的点的贡献,第一次出现这个颜色 c 时对答案无贡献,第二次才有,所以我们考虑记录每个点 前一个和他颜色相同的点 和 前一个点的前一个点,这样子类似于上一道题,我们用树状数组维护贡献,update(las[i],1),update(las[las[i]],-1),推理过程也和上一道题一样,注意可能会对 0 加减贡献,要直接 return。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=114514,M=2*1919810;
ll n,cl,m,a[M],ans[M];
ll las[M],lasc[M];
ll c[M];
ll lowbit(ll x){return x&-x;}
void update(ll x,ll k){
	if(!x) return;
	while(x<=n){
		c[x]+=k;
		x+=lowbit(x);
	}
}
ll query(ll x){
	if(!x) return 0;
	ll ans=0;
	while(x){
		ans+=c[x];
		x-=lowbit(x);
	}
	return ans;
}
struct que{
	ll l,r,id;
}q[M];
bool cmp(que x,que y){
	return x.r<y.r;
}
//这个维护挺智慧的 
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>cl>>m;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		las[i]=lasc[a[i]];
		lasc[a[i]]=i;
	}
	for(int i=1;i<=m;++i) cin>>q[i].l>>q[i].r,q[i].id=i;
	sort(q+1,q+m+1,cmp);
	ll pos=1;
	for(int i=1;i<=n;++i){
		update(las[i],1),update(las[las[i]],-1);
		while(q[pos].r==i&&pos<=m){
			ans[q[pos].id]=query(q[pos].r)-query(q[pos].l-1);
			++pos;
		}
	}
	for(int i=1;i<=m;++i) cout<<ans[i]<<'\n';
	return 0;
}

P8360 [SNOI2022] 军队

看到鬼畜操作考虑分块。先考虑如果没有修改颜色的操作怎么做:很简单,整块维护加法 tag 和块中每个 x 的个数,修改时散块暴力加整块加 tag 就行了。
然后考虑怎么解决改颜色的问题,这里引入一个 trick:值域并查集,在最初分块第二分块中都有用到。简单来说就是用并查集维护值域不大的信息。思路比较容易:我们初始记录下每个并查集里的元素个数 siz、父亲 fa、加法标记 tag 和这个并查集的颜色 col,我用了个结构体存。先搞 find 怎么写,我们考虑记录跳到的点的编号 id 和当前点的 tag,注意我们还是要路径压缩。我们顺便记录一个 sum 表示每个整块的权值和。
接下来考虑如何构建这个并查集:我们记录 fi 表示对于原序列中的 i 它所属的并查集的编号,再记录 rti,j 表示在第 i 块中的所有颜色为 j 的元素所在的并查集编号。构建方式如下:

点击查看代码
for(int i=1;i<=nq;++i){
		st[i]=ns*(i-1)+1,ed[i]=min(ns*i,n); //顺便把块分了,少点常数
		for(int j=st[i];j<=ed[i];++j){
			bel[j]=i,sum[i]+=a[j];
			if(!rt[i][c[j]]){
				rt[i][c[j]]=f[j]=++tot; //tot是新增并查集的编号
				t[tot].fa=t[tot].tag=0;
				t[tot].siz=1,t[tot].col=c[j];
			}
			else f[j]=rt[i][c[j]],++t[f[j]].siz; 
		}
	}

然后考虑修改颜色的操作。我们先考虑如何修改散块:我们直接遍历散块,直接对每一个元素进行 find,设得到的点编号为 p,其加法标记为 val,如果并查集的 p 点颜色不为 x 就直接跳过。否则就将 psiz 减一,如果减一后 siz=0 那么要清零: rt[bel[i]][t[p].col]=0。然后 ai 加上所得权值,而且注意,如果修改成的颜色 y 初始不存在,那么要在并查集里新给它开一个点,不能直接放在原来的 x 的点上,同时如果 y 在并查集里那么 ai 要减去 t[f[i]].tag,还有改大小这些比较显然的操作。

考虑对整块修改颜色,其实很简单,我们先取出两个颜色在并查集中的点,令 p1=rt[i][x],p2=rt[i][y];,如果 p10 那么跳过,有就合并到 p2 上,还有如果 p2=0 那么就直接改一下 p1 的颜色并且 swap 一下两个 rt 就行了,具体来说如下:

点击查看代码
if(p2){
	t[p2].siz+=t[p1].siz;
	t[p1].fa=p2;
	t[p1].tag-=t[p2].tag; 
	rt[i][x]=0;//这个要删了
}
else{
	t[p1].col=y;
	swap(rt[i][x],rt[i][y]);
}

然后其他操作就比较简单了。区间加的话就散块 find 一下,如果得到的点的颜色是 x 那么 aisum[bel[i]] 都加上 k,整块就直接把 rt[i][x] 提出来然后对并查集加 tag,对 sum[i] 也加一下就行了。

查询的话也简单,散块每个点 find 一下,然后 ans+=a[i]+u.val+t[u.id].tag;,注意 t[u.id].tag 是不包含在 u.val 中的,整块直接加维护的 sum 就行了。

然后就做完了,也不需要卡常,不需要逐块处理,细节也还好,比某些煞笔卡常题美好多了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define in inline
#define ll long long
const ll N=250005,M=1919810;
int n,m,C,tot;
int ns,nq,st[N],ed[N],bel[N];
ll a[N],sum[N];
int c[N],f[N],siz[N],rt[550][N]; //f单个点指向的根,rt每块每颜色的根 
struct xx{
	int fa,siz,col;
	ll tag;
}t[M];
struct gx{
	int id;
	ll val;
};
in gx find(int x){
	if(!t[x].fa) return (gx){x,0ll};
	gx y=find(t[x].fa);
	y.val+=t[x].tag;
	t[x].fa=y.id,t[x].tag=y.val;
	return y;
}
in void calc(int l,int r,int x,int y,int id){ //暴力修改散块颜色
	for(int i=l;i<=r;++i){
		gx u=find(f[i]);
		int p=u.id; ll val=u.val;
		if(t[p].col!=x) continue;
		if(!(--t[p].siz)) rt[id][t[p].col]=0;
		a[i]+=val+t[p].tag;
		if(!rt[id][y]){
			rt[id][y]=f[i]=++tot;
			t[tot].fa=t[tot].tag=0;
			t[tot].siz=1,t[tot].col=y;
		}
		else{
			f[i]=rt[id][y];
			a[i]-=t[f[i]].tag;
			++t[f[i]].siz;
		}
	}
}
in void update1(int l,int r,int x,int y){
	if(x==y) return;
	if(bel[l]==bel[r]){
		calc(l,r,x,y,bel[l]);
		return;
	}
	calc(l,ed[bel[l]],x,y,bel[l]);
	calc(st[bel[r]],r,x,y,bel[r]);
	for(int i=bel[l]+1;i<bel[r];++i){
		ll p1=rt[i][x],p2=rt[i][y];
		if(!p1) continue;
		if(p2){
			t[p2].siz+=t[p1].siz;
			t[p1].fa=p2;
			t[p1].tag-=t[p2].tag; 
			rt[i][x]=0;//删了
		}
		else{
			t[p1].col=y;
			swap(rt[i][x],rt[i][y]);
		}
	}
}
in void update2(int l,int r,int x,ll k){
	if(bel[l]==bel[r]){
		for(int i=l;i<=r;++i){
			gx u=find(f[i]);
			if(t[u.id].col==x) a[i]+=k,sum[bel[l]]+=k;
		}
		return;
	}
	for(int i=l;i<=ed[bel[l]];++i){
		gx u=find(f[i]);
		if(t[u.id].col==x) a[i]+=k,sum[bel[l]]+=k;
	}
	for(int i=st[bel[r]];i<=r;++i){
		gx u=find(f[i]);
		if(t[u.id].col==x) a[i]+=k,sum[bel[r]]+=k;
	}
	for(int i=bel[l]+1;i<bel[r];++i){
		ll p=rt[i][x];
		if(!p) continue; 
		t[p].tag+=k,sum[i]+=t[p].siz*k;
	}
}
in ll query(int l,int r){
	ll ans=0; 
	if(bel[l]==bel[r]){
		for(int i=l;i<=r;++i){
			gx u=find(f[i]);
			ans+=a[i]+u.val+t[u.id].tag;
		}
		return ans;
	}
	for(int i=l;i<=ed[bel[l]];++i){
		gx u=find(f[i]);
		ans+=a[i]+u.val+t[u.id].tag;
	}
	for(int i=st[bel[r]];i<=r;++i){
		gx u=find(f[i]);
		ans+=a[i]+u.val+t[u.id].tag;
	}
	for(int i=bel[l]+1;i<bel[r];++i) ans+=sum[i];
	return ans;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m>>C; ns=sqrt(n),nq=ceil(n*1.0/ns);
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=n;++i) cin>>c[i];
	for(int i=1;i<=nq;++i){
		st[i]=ns*(i-1)+1,ed[i]=min(ns*i,n);
		for(int j=st[i];j<=ed[i];++j){
			bel[j]=i,sum[i]+=a[j];
			if(!rt[i][c[j]]){
				rt[i][c[j]]=f[j]=++tot;
				t[tot].fa=t[tot].tag=0;
				t[tot].siz=1,t[tot].col=c[j];
			}
			else f[j]=rt[i][c[j]],++t[f[j]].siz; 
		}
	}
	//写不来逐块处理/fn
	for(int i=1;i<=m;++i){
		int opt,l,r,x,y; ll k;
		cin>>opt>>l>>r;
		if(opt==1){
			cin>>x>>y;
			update1(l,r,x,y);
		}
		if(opt==2){
			cin>>x>>k;
			update2(l,r,x,k);
		}
		if(opt==3) cout<<query(l,r)<<'\n';
		//debug();
	}
	return 0;
}//甚至不用加快读 
posted @   和蜀玩  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示