珂朵莉树
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;
}
要支持区间复制、交换和翻转。
复制、翻转保证区间长度相同且不重叠。
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));
}
一个点有颜色和数字,支持:
-
单点写数
-
区间着色
-
查询一段区间包含所有 \(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\) 会报错。