「CF911G Mass Change Queries」
题目大意
给出一个序列,支持区间将某个数变为某个数,输出最终的序列.
分析
序列中的元素只有 \(100\) 种,所以直接线段树上每个节点记录每个数出现的次数就好了.
但是,这样的做法时间复杂度并不优秀.
那么就从这个输出去考虑,可以发现在最终结果前没有必要知道每个点确切的值,那么考虑对每一种数建一颗线段树,将一种颜色变为令一种颜色只需要在原来的线段树中将修改区间部分分裂出来,在合并到变成的颜色的线段树中,最后遍历所有树就好了.
代码
#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int MAXN=2e5+7;
int n,m;
int arr[MAXN*2];
int answer[MAXN];
int root[MAXN*2];
struct SegmentTree//一颗动态开点的线段树
{
int lson,rson;
int num;
}sgt[MAXN*100];
int sgt_cnt=0,tot=0;
int rubbish[MAXN*100];//空间回收用的垃圾桶
#define LSON (sgt[now].lson)
#define RSON (sgt[now].rson)
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
#define NOW now_left,now_right
int NewPoint()//一个新个节点
{
if(tot)
{
return rubbish[tot--];//如果垃圾桶不是空的就从垃圾桶里拿
}
return ++sgt_cnt;//否则就用一个全新的
}
void DeletePoint(int &now)//删除节点
{
sgt[now].lson=sgt[now].rson=sgt[now].num=0;//注意要清空
rubbish[++tot]=now;//扔垃圾桶里
now=0;
}
void Merge(int &tree1,int &tree2,int left=1,int right=n)//合并两颗线段树
{
if(!tree1||!tree2)
{
tree1+=tree2;
tree2=0;//注意要将tree2改成0
return;
}
if(left==right)
{
sgt[tree1].num+=sgt[tree2].num;
DeletePoint(tree2);//删除节点,节省空间
return;
}
Merge(sgt[tree1].lson,sgt[tree2].lson,left,MIDDLE);//继续向下合并
Merge(sgt[tree1].rson,sgt[tree2].rson,MIDDLE+1,right);
DeletePoint(tree2);
}
void Split(int now_left,int now_right,int &tree1,int &tree2,int left=1,int right=n)//将线段树中的一段区间分裂出来,代码可以参考模板题中的题解
{
if(right<now_left||now_right<left)
{
return;
}
if(!tree1)
{
return;
}
if(now_left<=left&&right<=now_right)
{
tree2=tree1;
tree1=0;
return;
}
if(!tree2)
{
tree2=NewPoint();
}
Split(NOW,sgt[tree1].lson,sgt[tree2].lson,left,MIDDLE);
Split(NOW,sgt[tree1].rson,sgt[tree2].rson,MIDDLE+1,right);
}
void Updata(int place,int &now,int left=1,int right=n)//单点修改
{
if(place<left||right<place)
{
return;
}
if(!now)
{
now=NewPoint();
}
if(left==right)
{
sgt[now].num=1;//直接修改成1
return;
}
Updata(place,LEFT);
Updata(place,RIGHT);
}
void GetAnswer(int num,int now,int left=1,int right=n)//将这个线段树放到答案上
{
if(!now)
{
return;
}
if(left==right)
{
answer[left]+=sgt[now].num*num;//计算这个位置的数
return;
}
GetAnswer(num,LEFT);
GetAnswer(num,RIGHT);
}
int main()
{
scanf("%d",&n);
REP(i,1,n)
{
scanf("%d",&arr[i]);
Updata(i,root[arr[i]]);
}
scanf("%d",&m);
int cnt=n;
int left,right,x,y;
int split_root;
REP(i,1,m)
{
scanf("%d%d%d%d",&left,&right,&x,&y);
if(root[x])//如果在现在的序列中存在x那么修改
{
arr[++cnt]=y;//方便最后的查询,将y放入数组
split_root=0;
Split(left,right,root[x],split_root);//在序列中将这段区间的x分裂出来
Merge(root[y],split_root);//合并到y上面
}
}
sort(arr+1,arr+1+cnt);//排序去重
arr[0]=0;
REP(i,1,cnt)
{
if(arr[i]^arr[i-1])
{
GetAnswer(arr[i],root[arr[i]]);//记录答案
}
}
REP(i,1,n)
{
printf("%d ",answer[i]);//输出答案
}
return 0;
}