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

(有任何问题欢迎留言或私聊 && 欢迎交流讨论哦

题目:传送门

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

 题意就是求区间内有多少种颜色,但是带修改。
 如果不带修改就是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

猜你喜欢

转载自blog.csdn.net/qq_39599067/article/details/80703720