Codeforces 351D Jeff and Removing Periods(莫队+区间等差数列更新)

题目链接:http://codeforces.com/problemset/problem/351/D

题目大意:有n个数,每次可以删除掉数值相同并且所在位置成等差数列的数(只删2个数或者只删1个数应该也是可以的),删掉这些数以后可以将剩下的数重新以任意顺序排列,称为一次操作。现在给出m个询问,每个询问一个区间[l,r],问删光区间[l,r]中的数最少需要的操作次数。

解题思路: 由于一次操作之后可以以任意顺序排序,所以可以把相同数字排成等差的以便删除,那接下来就只要判断这个区间还有几种数字,一种数字操作一次就可以了,这个用莫队就能很快解决。但是问题在于第一次操作能不能将其中一种数字删尽,也就是在未排序情况下该区间是否有一种数字是成等差数列的。可以通过设置两个数组fl[i],fr[i],让fl[i]记录往左a[i]第一次不成等差的位置,fr[i]记录往右a[i]第一次不成等差的位置。这样只要相应地对add(),remove()做出修改就可以更新一段区间内的等差数列的个数了。具体看代码。

还有我个人犯的几个小错误,注意一下:

①使用的l,r应当是当前的L,R而不是查询区间的l,r。

②一开始没把add,remove弄明白,remove(pos)里的pos是即将要去掉的那个位置,add(pos)里的pos是即将要加上的那个位置

③fl[i]应该处理成a[i]往左找第一个不成等差的位置,而不是最后一个成等差的位置(fr[i]同理),比如1 2 1 3 5这组对1操作就会出错
(把i<=n写出i<=m,题目前面十几个数据都是n=m,害我一下没看出来,我脑抽了竟然找了半天)

  1 #include<iostream>
  2 #include<algorithm>
  3 #include<math.h>
  4 #include<stdio.h>
  5 #include<cstring>
  6 using namespace std; 
  7 const int N=1e5+5;
  8 
  9 int ans,unit,dc;
 10 //fl[i]记录a[i]往左第一个不成等差的位置,fr[i]记录a[i]往右第一个不成等差的位置
 11 //pre[i]记录从左往右前一个a[i]的位置,bak[i]记录从右往左前一个a[i]的位置,last[a[i]]记录a[i]最后出现位置 
 12 int a[N],cnt[N],res[N],pre[N],bak[N],last[N],fl[N],fr[N];
 13 
 14 struct node{
 15     int l,r;
 16     int id;
 17 }q[N];
 18 
 19 bool cmp(node a,node b){
 20     return a.l/unit==b.l/unit?a.r<b.r:a.l/unit<b.l/unit;    
 21 }
 22 
 23 void addl(int pos,int r){
 24     cnt[a[pos]]++;
 25     if(cnt[a[pos]]==1){
 26         dc++;
 27         ans++;
 28     }
 29     
 30     else if(fr[pos]<=r&&fr[bak[pos]]>r) dc--;//加上a[pos]之后不成等差&&加上之前成等差
 31 }
 32 
 33 void removel(int pos,int r){
 34     cnt[a[pos]]--;
 35     if(cnt[a[pos]]==0){
 36         dc--;
 37         ans--;
 38     }
 39     else if(fr[pos]<=r&&fr[bak[pos]]>r)    dc++;//去掉a[pos]之前不成等差&&去掉之后成等差
 40 }
 41 
 42 void addr(int pos,int l){
 43     cnt[a[pos]]++;
 44     if(cnt[a[pos]]==1){
 45         dc++;
 46         ans++;
 47     }
 48     else if(fl[pos]>=l&&fl[pre[pos]]<l)    dc--;//同理 
 49 }
 50 
 51 void remover(int pos,int l){
 52     cnt[a[pos]]--;
 53     if(cnt[a[pos]]==0){
 54         dc--;
 55         ans--;
 56     }
 57     else if(fl[pos]>=l&&fl[pre[pos]]<l)    dc++;
 58 }
 59 
 60 int main(){
 61     int n;
 62     scanf("%d\n",&n);
 63     unit=sqrt(n);
 64     for(int i=1;i<=n;i++){
 65         scanf("%d",&a[i]);
 66     }
 67     //预处理fl,fr 
 68     for(int i=1;i<=n;i++){
 69         pre[i]=last[a[i]];
 70         last[a[i]]=i;
 71         //从左往右,当a[i]个数小于等于两个时,左边界为0 
 72         if(pre[pre[i]]==0)
 73             fl[i]=0;
 74         else if(pre[pre[i]]-pre[i]==pre[i]-i)
 75             fl[i]=fl[pre[i]];
 76         else
 77             fl[i]=pre[pre[i]];
 78     }
 79     //清空last 
 80     memset(last,0,sizeof(last));
 81     for(int i=n;i>=1;i--){
 82         bak[i]=last[a[i]];
 83         last[a[i]]=i;
 84         //从右往左,当a[i]个数小于等于两个时,右边界为n+1 
 85         if(bak[bak[i]]==0)
 86              fr[i]=n+1;
 87         else if(bak[bak[i]]-bak[i]==bak[i]-i)
 88             fr[i]=fr[bak[i]];
 89         else
 90             fr[i]=bak[bak[i]];
 91     }
 92     
 93     
 94     int m;
 95     scanf("%d",&m);
 96     for(int i=1;i<=m;i++){
 97         scanf("%d%d",&q[i].l,&q[i].r);
 98         q[i].id=i;
 99     }
100     
101     sort(q+1,q+1+m,cmp);
102     
103     int L=q[1].l,R=L-1;
104     for(int i=1;i<=m;i++){
105         //注意传入add,remove的左右端点是L,R而不是q[i].l,q[i].r 
106         while(L>q[i].l)
107             addl(--L,R);
108         while(L<q[i].l)
109             removel(L++,R);
110         while(R<q[i].r)
111             addr(++R,L);
112         while(R>q[i].r)
113             remover(R--,L);
114         if(dc>0)
115             res[q[i].id]=ans;
116         else
117             res[q[i].id]=ans+1;
118     }
119     
120     for(int i=1;i<=m;i++){
121         printf("%d\n",res[i]);
122     }
123 }

 

posted @ 2017-07-20 00:36  Yeader  阅读(467)  评论(0编辑  收藏  举报