• [国家集训队]数颜色 / 维护队列


    IV.III.[国家集训队]数颜色 / 维护队列

    虽然这里这题的写法是CDQ但是我还是要大声喊出:树套树yyds!

    首先,众所周知地,我们可以对于每个位置记录其颜色上一次出现的地方,记作 \(las_i\),则我们需要知道的就是 \([l,r]\)\(las_i<l\) 的总数。

    众所周知地,这是二维数点问题。现在有了修改操作,需要保证时间顺序,因此变成了大家都喜欢的三维数点问题。

    具体操作很简单:因为观察到一次修改最多改变 \(3\) 个位置的 \(las\) 值,放到二维数点的平面上就是 \(6\) 次修改,于是预处理这六次修改的样式,然后CDQ分治时计算左侧的修改对右侧询问的影响就行了。可以采用树状数组简单处理。

    但是CDQ分治也有其优点,就是空间复杂度是 \(O(n)\)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,a[150010],lim,las[150010],res[150010],t[150010];
    vector<int>v;
    struct node{
    	int tp,a,b;
    	node(){}
    	node(int T,int A,int B){tp=T,a=A,b=B;}
    	friend bool operator<(const node&x,const node&y){return x.a==y.a?!x.tp>!y.tp:x.a<y.a;}
    }q[150010];
    char str[10];
    set<int>s[300010];
    void ADD(int x,int y){while(x<=n)t[x]+=y,x+=x&-x;}
    int SUM(int x){int ret=0;while(x)ret+=t[x],x-=x&-x;return ret;}
    vector<node>u,w[150010]; 
    void CDQ(int l,int r){
    	if(l==r)return;
    	int mid=(l+r)>>1;
    	for(int i=l;i<=mid;i++)if(!q[i].tp)for(auto j:w[i])u.push_back(j);
    	for(int i=mid+1;i<=r;i++)if(q[i].tp)for(auto j:w[i])u.push_back(j);
    	sort(u.begin(),u.end());
    	for(auto i:u){
    		if(!i.tp){
    			if(i.b>0)ADD(i.b,1);
    			else ADD(-i.b,-1);
    		}else{
    			if(i.tp>0)res[i.tp]+=SUM(i.b);
    			else res[-i.tp]-=SUM(i.b);
    		}
    	}
    	u.clear();
    	for(auto i:u)if(!i.tp){
    		if(i.b>0)ADD(i.b,-1);
    		else ADD(-i.b,1);
    	}
    	CDQ(l,mid),CDQ(mid+1,r);
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)scanf("%d",&a[i]),v.push_back(a[i]);
    	for(int i=1;i<=m;i++){
    		scanf("%s%d%d",str,&q[i].a,&q[i].b),q[i].tp=(str[0]=='Q');
    		if(str[0]=='R')v.push_back(q[i].b);
    	}
    	sort(v.begin(),v.end()),v.resize(lim=unique(v.begin(),v.end())-v.begin());
    	for(int i=1;i<=n;i++)s[a[i]=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1].insert(i);
    	for(int i=1;i<=m;i++)if(!q[i].tp)q[i].b=lower_bound(v.begin(),v.end(),q[i].b)-v.begin()+1;
    	for(int i=1;i<=n;i++){
    		auto tmp=s[a[i]].lower_bound(i);
    		if(tmp!=s[a[i]].begin())las[i]=1+*--tmp;
    		else las[i]=1;
    	}
    	for(int i=1;i<=m;i++){
    		if(!q[i].tp)continue;
    		u.emplace_back(-i,q[i].a-1,q[i].a),u.emplace_back(i,q[i].b,q[i].a);
    		w[i].emplace_back(-i,q[i].a-1,q[i].a),w[i].emplace_back(i,q[i].b,q[i].a);
    	}
    	sort(u.begin(),u.end());
    	for(int i=0,j=1;i<u.size();i++){
    		while(j<=u[i].a)ADD(las[j++],1);
    		if(u[i].tp>0)res[u[i].tp]+=SUM(u[i].b);else res[-u[i].tp]-=SUM(u[i].b);
    	}
    	u.clear(),memset(t,0,sizeof(t));
    	for(int i=1;i<=m;i++){
    		if(q[i].tp)continue;
    		int P=q[i].a,C=q[i].b;
    		if(C==a[P])continue;
    		w[i].emplace_back(0,P,-las[P]);
    		s[a[P]].erase(P);
    		auto tmp=s[a[P]].lower_bound(P);
    		if(tmp!=s[a[P]].end())w[i].emplace_back(0,*tmp,-las[*tmp]),w[i].emplace_back(0,*tmp,las[*tmp]=las[P]);
    		
    		tmp=s[C].lower_bound(P);
    		if(tmp!=s[C].end()){
    			w[i].emplace_back(0,P,las[P]=las[*tmp]);
    			w[i].emplace_back(0,*tmp,-las[*tmp]);
    			w[i].emplace_back(0,*tmp,las[*tmp]=P+1);
    		}else if(tmp!=s[C].begin())w[i].emplace_back(0,P,las[P]=1+*--tmp);
    		else w[i].emplace_back(0,P,las[P]=1);
    		a[P]=C;
    		s[C].insert(P);
    	}
    	CDQ(1,m);
    	for(int i=1;i<=m;i++)if(q[i].tp)printf("%d\n",res[i]);
    	return 0;
    } 
    

  • 相关阅读:
    字符串 列表 元组 字典 集合3.4列表
    判断语句和循环语句2.12 for循环
    字符串 列表 元组 字典 集合3.3字符串常见操作
    判断语句和循环语句2.13 break和continue
    字符串 列表 元组 字典 集合3.5列表的常见操作
    c#生成json类
    JQuery UI Datepicker中文显示的方法
    ReportViewer无法直接打印的问题
    C++与SQL数据库 使用基础
    WM 6.0 中的设备控制启用设备与禁用设备
  • 原文地址:https://www.cnblogs.com/Troverld/p/14620786.html
Copyright © 2020-2023  润新知