BZOJ-2120-数颜色-带修改莫队and暴力分块

(有任何问题欢迎留言或私聊 && 欢迎交流讨论哦
莫队二次离线
树上:1, 2
qita

题目:传送门

 (原题目描述及样例在最下面)

 题意就是求区间内有多少种颜色,但是带修改。
 如果不带修改就是SPOJ-DQUERY,可以莫队,主席树,树状数组搞。
 如果有修改操作,分块暴力搞,莫队搞搞也行。(其实是我不会树套树。树状数组可以写吗?

分块:672ms
莫队:492ms


实测:块的大小为**2*sqrt(n)**最快

 (为什么我的cmp函数不加const就CE,一摸一样的代码给别人交就过了,我交就CE了,艹)

分块:

思路参考这位大神:传送门

 分块记录每个数字上一次出现的位置和最后出现的位置。
 如果在[L, R]区间内数字上一次出现的位置在L前面表示此区间这个数第一次出现,然后ans++。

 修改的话你只需要修改 原数字的这一条链和修改后的数字这一条链。
 pre数组就像链表一样记录前驱。所以只需要改变这两条链就行了,而不用暴力修改所有区间。

AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 50005;
int l, r;
int a[maxn], belog[maxn], n, m, block, pre[maxn], spre[maxn]; //pre[i]=j表示数字a[i]上一次出现在j位置
int lst[1000006]={0,};//lst[i]=j,表示数字i上一次出现的位置是j ·
char op[2];
inline int read() {//读入挂
    int ret = 0, c, f = 1;
    for(c = getchar(); !(isdigit(c) || c == '-'); c = getchar());
    if(c == '-') f = -1, c = getchar();
    for(; isdigit(c); c = getchar()) ret = ret * 10 + c - '0';
    if(f < 0) ret = -ret;
    return ret;
}
inline void reset(int x) {
    int l = (x - 1) * block + 1;
    int r = min(n, x * block) ;
    for(int i = l; i <= r; i++) {
        spre[i] = pre[i];
    }
    sort(spre + l, spre + r + 1);
}
inline void build() {
    for(int i = 1; i <= n; i++) {
        pre[i] = lst[a[i]];
        lst[a[i]] = i;
    }
    for(int i = 1; i <= belog[n]; i++) {
        reset(i);
    }
}
inline void query(int l, int r) {
    int t1 = belog[l];
    int t2 = belog[r];
    int ans = 0;
    for(int i = l; i <= min(t1 * block, r); i++) {
        if(pre[i] < l) ans++;
    }
    if(t1 != t2) {
        for(int i = (t2 - 1) * block + 1; i <= r; i++) {
            if(pre[i] < l) ans++;
        }
        for(int i = t1 + 1; i <= t2 - 1; i++) {
            ans += lower_bound(spre + (i - 1) * block + 1, spre + i * block + 1, l) - (spre + (i - 1) * block + 1); //不能用upper_bound()
        }
    }
    printf("%d\n", ans);
}
inline void change(int l, int r) {
    if(a[l]==r)return;
    int ha=belog[l],hb=1,tl=belog[l],hc=1;
    for(int i=lst[a[l]];i>=l;i=pre[i]){
        if(pre[i]==0){
            lst[a[l]]=0;
        }else if(pre[i]==l){
            pre[i]=pre[l];
            ha=belog[i];
            break;
        }else if(i==l){
            lst[a[l]]=pre[i];
            break;
        }
    }
    int flag=1;
    for(int i=lst[r];i>=l;i=pre[i]){
        if(pre[i]<l){
            flag=0;
            int t=pre[i];
            pre[i]=l;
            pre[l]=t;
            hb=belog[i];
            break;
        }
    }
    if(lst[r]==0){
        pre[l]=lst[r];
        lst[r]=l;
        hb=belog[l];
    }else if(flag&&lst[r]<l){
        hc=belog[lst[r]];
        pre[l]=lst[r];
        lst[r]=l;
        hb=belog[l];
    }
    a[l]=r;
    reset(ha);reset(hb);reset(tl);reset(hc);
}
int main() {
    n = read();
    m = read();
    block = sqrt(n*1.0)*2;
    for(int i = 1; i <= n; i++) {
        a[i] = read();
        belog[i] = (i - 1) / block + 1;
    }
    build();
    for(int i = 1; i <= m; i++) {
        scanf("%s%d%d", op, &l, &r);
        if(op[0] == 'Q') {
            query(l, r);
        } else {
            change(l, r);
        }
    }
    return 0;
}


莫队:

 查询的话就是和SPOJ-DQUERY一样,就不多讲了。

 莫队除了用到已知区间[L, R],还要用到 T 记录已经进行了多少次修改操作

 修改操作有点骚。它也是离线下来,记录修改的编号。每个询问也要记录在这次询问前进行了多少次修改。

 查询前要让已经进行的修改操作 T 恰好等于这次查询所记录的次数。

AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
struct lp{
	int l,r,id,last;
	int old,now;
}cw[N],tim[N];
int n,m,ar[N],arr[N],belong[N],ans,Ans[N],cnt[N*10];
bool cmp(const lp &a,const lp &b){
	if(belong[a.l]!=belong[b.l])return belong[a.l]<belong[b.l];
	if(belong[a.r]!=belong[b.r])return belong[a.r]<belong[b.r];
	return a.last<b.last;
}
void update(int x,int f){
	cnt[ar[x]]+=f;
	if(f==1&&cnt[ar[x]]==1)ans++;
	if(f==-1&&cnt[ar[x]]==0)ans--;
}
void update_time(int t,int f,int l,int r){
	if(tim[t].l>=l&&tim[t].l<=r){
		cnt[tim[t].old]-=f;
		cnt[tim[t].now]+=f;
		if(f==1){
			if(cnt[tim[t].old]==0)ans--;
			if(cnt[tim[t].now]==1)ans++;
		}else{
			if(cnt[tim[t].old]==1)ans++;
			if(cnt[tim[t].now]==0)ans--;
		}
	}
	if(f==1)ar[tim[t].l]=tim[t].now;
	else ar[tim[t].l]=tim[t].old;
}
int main() {
	while(~scanf("%d%d",&n,&m)){
		int block=2*sqrt(n*1.0);
		for(int i=1;i<=n;++i){
			scanf("%d",&ar[i]);
			arr[i]=ar[i];
			belong[i]=(i-1)/block+1;
		}
		char op[2];
		int tot=0,change=0;
		for(int i=0,l,r;i<m;++i){
			scanf("%s%d%d",op,&l,&r);
			if(op[0]=='Q'){
				cw[tot].last=change;cw[tot].l=l;cw[tot].r=r;
				cw[tot].id=tot;tot++;
			}else{
				tim[++change].l=l;
				tim[change].old=arr[l];
				tim[change].now=r;
				arr[l]=r;
			}	
		}
		ans=0;
		memset(cnt,0,sizeof(cnt));
		sort(cw,cw+tot,cmp);
		for(int i=0,L=1,R=0,t=0;i<tot;++i){
			for(;t<cw[i].last;){
				update_time(++t,1,L,R);
			}
			for(;t>cw[i].last;){
				update_time(t--,-1,L,R);
			}
			while(L<cw[i].l)update(L++,-1);
			while(L>cw[i].l)update(--L,1);
			while(R<cw[i].r)update(++R,1);
			while(R>cw[i].r)update(R--,-1);
			Ans[cw[i].id]=ans;
		}
		for(int i=0;i<tot;++i){
			printf("%d\n",Ans[i] );
		}
	}    
    return 0;
}

题目描述:

Description
墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会像你发布如下指令: 1、 Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔。 2、 R P Col 把第P支画笔替换为颜色Col。为了满足墨墨的要求,你知道你需要干什么了吗?

Input
第1行两个整数N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。第2行N个整数,分别代表初始画笔排中第i支画笔的颜色。第3行到第2+M行,每行分别代表墨墨会做的一件事情,格式见题干部分。

Output
对于每一个Query的询问,你需要在对应的行中给出一个数字,代表第L支画笔到第R支画笔中共有几种不同颜色的画笔。

Sample Input
6 5

1 2 3 4 5 5

Q 1 4

Q 2 6

R 1 2

Q 1 4

Q 2 6

Sample Output
4

4

3

4

HINT
对于100%的数据,N≤10000,M≤10000,修改操作不多于1000次,所有的输入数据中出现的所有整数均大于等于1且不超过10^6。

2016.3.2新加数据两组by Nano_Ape

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值