题目链接: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); 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 ",res[i]); 122 } 123 }