带修改的莫队算法【JZOJ 4594】【UVa 12345】Dynamic len

Dynamic len(set(a[L:R]))

 UVA - 12345 

题意:给出下标为 0 到 n - 1 的n个数,(后面处理为 1 到 n 的)有两种操作

1.询问从 x 到 y 这个区间内有多少个不一样的数

2.把下标为 x 的数的值改为 y

思路:区间询问,离线查询,可以用莫队,但是是带修改操作的莫队,带修莫队的分块大小为N^(2/3)N23N23

在输入操作的时候,每一个步骤都要记录时间戳,因为有些修改是在查询之前,有些修改是在查询之后的,所以每个操作都要有一个tm标签,记录它的时间,然后对于查询操作,要用一个id记录询问顺序。

然后对询问进行排序,按照分块排序。

然后从前往后进行询问操作,一遍移动时间戳,把该询问时间戳前面的修改都做了,若时间戳在该询问之后,那么就把修改改回去,所以修改操作还要记录修改前的那个值,修改做完了就去移动 l r 标签到对于区间,然后记录答案

带修莫队中最重要的与非带修的不同的地方是 push() 函数,它必须是可以双向操作的,其他的add() 和 dele() 跟一般的是差不多的,还有一个核心就是得记录所有操作的时间戳。

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

#define maxn 50004
#define maxm 1000005 

struct node1{
	int l,r,id,tm;
}ask[maxn];
struct node2{
	int last,x,val;
}chg[maxn];
int n,m,tot,cnt,Ans,pos[maxn],last[maxn],a[maxn];
int num[maxm],ans[maxm],flag[maxm];

bool cmp(node1 a,node1 b){	//按照分块对询问排序 
	if(pos[a.l] != pos[b.l])
		return pos[a.l] < pos[b.l];
	if(pos[a.r] != pos[b.r])
		return pos[a.r] < pos[b.r];
	return a.tm < b.tm;
}
void dele(int x){
	if(!(--num[a[x]]))//如果去掉该点的值后,该值没了,计数减 
		Ans--;
	flag[x] = 0;	//标记该点不在记录中 
}
void add(int x){
	if(!num[a[x]]) //如果该点的值没有出现过,计数加 
		Ans++;
	num[a[x]]++;
	flag[x] = 1;	//标记该点已在记录中 
}
void push(int x){	//如果这个点原本有,那就删掉,没有就加上 
	if(flag[x])		//这个操作是因为后面有反向操作 
		dele(x);
	else
		add(x);
}
void modify(int x,int v){
	if(flag[x]){	// 改变这个点的值,如果原本有值,删掉后赋值,没有就直接赋值 
		dele(x);
		a[x] = v;
		add(x);
	}else{
		a[x] = v;
	}
}
int main(){
	scanf("%d %d",&n,&m);
	int s = pow(n,2.0 / 3.0) + 1; // 带修改的莫队,分块为 n ^ (2/3) 
	for(int i = 1;i <= n;i++){
		scanf("%d",&a[i]);
		last[i] = a[i];		//记录该位置最后的值 
		pos[i] = (i + 1) / s;
	}
	for(int i = 1;i <= m;i++){
		int x,y;
		char opt[5];
		scanf("%s %d %d",opt,&x,&y);
		x++;
		if(opt[0] == 'Q'){
			ask[++tot].id = tot; //tot记录询问顺序,cnt记录时间顺序,包括修改的 
			ask[tot].l = x;
			ask[tot].r = y;
			ask[tot].tm = cnt;
		}else{
			chg[++cnt].x = x;
			chg[cnt].last = last[x]; //由于修改的返回去要修改回去,所以要记录修改前的值 
			chg[cnt].val = y;
			last[x] = y;
		}
	}
	sort(ask + 1,ask + 1 + tot,cmp);
	int l = 1,r = 0,tm = 0;
	for(int i = 1;i <= tot;i++){
		if(tm < ask[i].tm){	// 把这个询问时间前的修改全部做了 
			for(int j = tm + 1;j <= ask[i].tm;j++)
				modify(chg[j].x,chg[j].val);
		}else{	//若是询问在当前时间后面,那么就要把修改了的改回去 
			for(int j = tm;j >= ask[i].tm + 1;j--)
				modify(chg[j].x,chg[j].last);
		}
		while(l < ask[i].l){
			push(l++);
		}
		while(l > ask[i].l){
			push(--l);
		}
		while(r < ask[i].r){
			push(++r);
		}
		while(r > ask[i].r){
			push(r--);
		}
		ans[ask[i].id] = Ans;
		tm = ask[i].tm;
	}
	for(int i = 1;i <= tot;i++)
		printf("%d\n",ans[i]);
	return 0;
} 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值