珂朵莉树

Willem, Chtholly and Seniorious

写一个数据结构,支持 区间加,区间赋值,区间第k小,区间幂次和

ODT把相同的一段区间视作一个节点,即:

struct node{
	int l,r;
	mutable ll val;
	bool operator<(const node &x)const{
		return l<x.l;
	}
	node(int a,int b,ll c){
		l=a,r=b,val=c;
	}
	node(int a){l=a;}
};

一个节点的迭代器 \(it\) 是常量迭代器,但是 \(val\) 可能需要进行修改,但 \(\text{mutable}\) 正好能够支持修改常量。


宏定义迭代器,再通过 \(\text{set}\) 建立一棵树。

#define sit set<node>::iterator
set<node>s;

\(\text{Split}\)

拆分节点。

sit Split(ll x){
	sit it=s.lower_bound(node(x));
	if(it!=s.end()&&it->l==x)return it;//若当前节点的左端点为分裂的节点,直接返回
	it--;//分裂上一个
	int l=it->l,r=it->r;ll val=it->val;
	s.erase(it);
	s.insert(node(l,x-1,val));
	return s.insert(node(x,r,val)).first;//返回分裂位置(新插入节点)的迭代器 
}

\(\text{Assign}\)

区间推平,保证了ODT的复杂度。

void Assign(int l,int r,ll val){
	sit it2=Split(r+1),it1=Split(l);
	s.erase(it1,it2);//删除[l,r+1)的节点
	s.insert(node(l,r,val)); 
}

必须先声明 \(it2\) 再声明 \(it1\) ,否则 \(\text{Split}\) 中的 \(\text{erase}\) 可能使迭代器 \(it1\) 失效(其所属节点被删除),后面同理。


区间加

取出 \(\lbrack l,r \rbrack\) 的节点分别加上。

void modify(int l,int r,ll val){
	sit it2=Split(r+1),it1=Split(l);
	for(sit it=it1;it!=it2;it++)
		it->val+=val; 
}

区间第k小

取出 \(\lbrack l,r \rbrack\) 的节点暴力 \(\text{sort}\) 即可。

ll kth(int l,int r,int k){
	sit it2=Split(r+1),it1=Split(l);
	vector< pair<ll,int> >buc;
	for(sit it=it1;it!=it2;it++)
		buc.push_back(make_pair(it->val,it->r-it->l+1));
	sort(buc.begin(),buc.end());
	for(int i=0;i<buc.size();i++){
		k-=buc[i].second;
		if(k<=0)return buc[i].first;
	}
}

区间幂次和

直接暴力即可。

ll qpow(ll k,ll b,ll p){
	ll ret=1;k%=p;
	while(b){
		if(b&1)(ret*=k)%=p;
		(k*=k)%=p,b>>=1; 
	}
	return ret;
}
ll sum(int l,int r,ll x,ll y){//y为模数 
	sit it2=Split(r+1),it1=Split(l);
	ll ret=0;
	for(sit it=it1;it!=it2;it++)
		(ret+=(it->r-it->l+1)*qpow(it->val,x,y)%y)%=y;
	return ret;
}

复杂度参考 这里

本题数据随机,节点总数均摊 \(O(log \space n)\) ,足以暴力通过。

#include<bits/stdc++.h>
#define ll long long
#define N 100010
#define Mod 1000000007
#define sit set<node>::iterator
using namespace std;
struct node{
	int l,r;
	mutable ll val;
	bool operator<(const node &x)const{
		return l<x.l;
	}
	node(int a,int b,ll c){
		l=a,r=b,val=c;
	}
	node(int a){l=a;}
};
set<node>s;
sit Split(ll x){
	sit it=s.lower_bound(node(x));
	if(it!=s.end()&&it->l==x)return it;
	it--;
	int l=it->l,r=it->r;ll val=it->val;
	s.erase(it);
	s.insert(node(l,x-1,val));
	return s.insert(node(x,r,val)).first;
}
void Assign(int l,int r,ll val){
	sit it2=Split(r+1),it1=Split(l);
	s.erase(it1,it2);
	s.insert(node(l,r,val)); 
}
void modify(int l,int r,ll val){
	sit it2=Split(r+1),it1=Split(l);
	for(sit it=it1;it!=it2;it++)
		it->val+=val; 
}
ll kth(int l,int r,int k){
	sit it2=Split(r+1),it1=Split(l);
	vector< pair<ll,int> >buc;
	for(sit it=it1;it!=it2;it++)
		buc.push_back(make_pair(it->val,it->r-it->l+1));
	sort(buc.begin(),buc.end());
	for(int i=0;i<buc.size();i++){
		k-=buc[i].second;
		if(k<=0)return buc[i].first;
	}
	return -1;
}
ll qpow(ll k,ll b,ll p){
	ll ret=1;k%=p;
	while(b){
		if(b&1)(ret*=k)%=p;
		(k*=k)%=p,b>>=1; 
	}
	return ret;
}
ll sum(int l,int r,ll x,ll y){
	sit it2=Split(r+1),it1=Split(l);
	ll ret=0;
	for(sit it=it1;it!=it2;it++)
		(ret+=(it->r-it->l+1)*qpow(it->val,x,y)%y)%=y;
	return ret;
}
int n,m,opt,l,r;
ll x,y;
ll ret,seed,vmax;
ll rnd(){
	ret=seed,seed=(seed*7+13)%Mod;
	return ret;
}
int main(){
	scanf("%d%d%lld%lld",&n,&m,&seed,&vmax);
	for(int i=1,tp;i<=n;i++)
		s.insert(node(i,i,rnd()%vmax+1));
	s.insert(node(n+1,n+1,0));//保险
	while(m--){
		opt=rnd()%4+1,l=rnd()%n+1,r=rnd()%n+1;
		if(l>r)swap(l,r);
		if(opt==3)x=rnd()%(r-l+1)+1;
		else x=rnd()%vmax+1;
		if(opt==1)modify(l,r,x);
		if(opt==2)Assign(l,r,x);
		if(opt==3)printf("%lld\n",kth(l,r,x));
		if(opt==4)y=rnd()%vmax+1,printf("%lld\n",sum(l,r,x,y));
	}
	
	
	return 0;
} 

P5350 序列

要支持区间复制、交换和翻转。

复制、翻转保证区间长度相同且不重叠。

struct node{
	int l,r;
	mutable ll val;
    node(int _l=0,int _r=0,ll _val=0):l(_l),r(_r),val(_val){}
	bool operator<(const node &x)const{
		return l<x.l;
	}
};

这样定义方便点,不然会报错。

复制相当于把一段迭代器删除再暴力添加。

node t[N];
void cpy(int l1,int r1,int l2,int r2){
	int len=0;
	sit ir1=Split(r1+1),il1=Split(l1);
	for(sit it=il1;it!=ir1;it++)
		t[++len]=(node){it->l,it->r,it->val};
	sit ir2=Split(r2+1),il2=Split(l2);
	s.erase(il2,ir2);
	for(int i=1;i<=len;i++)
		s.insert(node(t[i].l-l1+l2,t[i].r-l1+l2,t[i].val));
}

替换同理,注意要判区间位置,不是很清楚。

node t2[N];
void swp(int l1,int r1,int l2,int r2){
    if(l1>l2)swap(l1,l2),swap(r1,r2);
	int len1=0,len2=0;
	sit ir1=Split(r1+1),il1=Split(l1);
	for(sit it=il1;it!=ir1;it++)
		t[++len1]=(node){it->l,it->r,it->val};
	s.erase(il1,ir1);
	sit ir2=Split(r2+1),il2=Split(l2);
	for(sit it=il2;it!=ir2;it++)
		t2[++len2]=(node){it->l,it->r,it->val};
	s.erase(il2,ir2);
	for(int i=1;i<=len1;i++)
		s.insert(node(t[i].l-l1+l2,t[i].r-l1+l2,t[i].val));
	for(int i=1;i<=len2;i++)
		s.insert(node(t2[i].l-l2+l1,t2[i].r-l2+l1,t2[i].val));
}

翻转也不难,操作 \(\lbrack L,R\rbrack\) 时,一个区间 \(\lbrack l,r\rbrack\) 就会变成 \(\lbrack L+R-r,L+R-l\rbrack\) .

void flp(int l,int r){
	int len=0;
	sit it2=Split(r+1),it1=Split(l);
	for(sit it=it1;it!=it2;it++)
		t[++len]=(node){it->l,it->r,it->val};
	s.erase(it1,it2);
	for(int i=1;i<=len;i++)
		s.insert(node(l+r-t[i].r,l+r-t[i].l,t[i].val));
}

P5251 [LnOI2019]第二代图灵机

一个点有颜色和数字,支持:

  • 单点写数

  • 区间着色

  • 查询一段区间包含所有 \(c\) 种颜色的子区间的最小数字和,无符合子区间返回 \(-1\) .

  • 查询一段区间无重复颜色的子区间的最大数字和。

\(n,m\le 10^5,c\le 100,1\le V\le 10^4\) .

容易发现数字要在线段树上维护。

如何做第 \(3\) 个?

\(c=1\) 时答案是这段区间的最小值。

可以想到以迭代器作为双指针来做。

维护区间颜色总数为 \(c\) ,记当前左指针 \(itl\) ,右指针 \(it\) ,最小值取 \(\lbrack itl\rightarrow r,it\rightarrow l\rbrack\)(左有头无尾,右有尾无头) .

ll minn(int l,int r){
	if(c==1)return qmin(1,1,n,l,r);
	sit itr=split(r+1),itl=split(l),it=itl;
	memset(cnt,0,sizeof(cnt)),tot=0;
	ll ret=inf;
	while(it!=itr){
		add(it->val);
		while(tot==c){
			ret=min(ret,qsum(1,1,n,itl->r,it->l));
			del(itl->val),itl++;
		}
		it++;
	}
	return ret==inf?-1:ret;
}

做第 \(4\) 个同理,维护指针之间 没有数量 \(>1\) 的颜色 ,最小值同理。

遇到 \(it\rightarrow l \not= it\rightarrow r\) 时,就要移动左指针。

最坏情况是这段区间的最大值。

ll maxx(int l,int r){
	memset(cnt,0,sizeof(cnt));
	sit itr=split(r+1),itl=split(l),it=itl;
	ll ret=qmax(1,1,n,l,r);
	while(it!=itr){
		cnt[it->val]++;
		while(itl!=it&&cnt[it->val]>1)
			cnt[itl->val]--,itl++;
		if(it!=itl)
			ret=max(ret,qsum(1,1,n,itl->r,it->l));
		if(it->l!=it->r){
			while(itl!=it)
				cnt[itl->val]--,itl++;
		}
		it++;
	}
	return ret;
}

然后这个困难题就写完了。

线段树结构体定义了 \(l,r\) 会报错。

posted @ 2023-08-06 19:52  SError  阅读(18)  评论(0编辑  收藏  举报