傻b错误调一天系列
原题:
大意:给你一个数列a,字词两种操作:
1.把c[l]改成r
2.询问在区间[l,r]中,有多少个极大子区间满足子区间里的数全部一样,且在[x,y]范围内
(对于满足条件的区间A,若不存在满足条件的区间B使得A包含于B,则称A为极大子区间)
序列问题,要求复杂度O(nlogn),联想cdq分治
值域可以容斥拆成[1,l-1]和[1,r]两个询问,即把询问转化为区间中数小于等于x的数有多少个
可以把初始数列看成0,然后用修改操作代替初始数列
那么现在就存在偏序:若修改A的时间小于询问B,且A的值小于B的值,则A可以给B提供贡献
对于计算贡献,我们建一个线段树,字词单点修改,并查询有多少个不同的非0极大子区间,这个比较好写
初始按时间排序,然后按值域分治
然后这题就做完了马
反例:
6 3
1 1 4 5 1 4
2 1 6 2 5
1 6 1
2 1 6 2 5
上面的做法会输出3 3,正确答案却是3 2
考试的时候我写这个做法然后深度自闭
出错的原因是因为我们按值域分治,那么当第6个数(被视为操作1 6 4)和第三个操作(2 1 6 2 5)分到一个分治区间时,因为操作2(1 6 1)被分到左边的分治区间了,所以不会把代表第6个数的操作覆盖掉,这时第6个数就给第3个操作产生了贡献(尽管第三个操作进行时它已经被覆盖掉了)
更换提供贡献的顺序是不行的,这是个死循环,不管是用前序、中序或后序cdq分治都无法解决,根本原因是操作2(1 6 1)实际上给操作3(2 1 6 2 5)一个负贡献(它把一个本来合法的区间变得不合法),却因为值域没有达到操作3的范围([2,5])而被忽略了
这个负贡献在分治过程中无法统计(至少我没找到方法)
正确做法是
只考虑一个区间中的左端点那个数,新建一个数组b,对于每个b[i],若a[i]==a[i-1]则b[i]为0否则为a[i]
问题就转化为求区间[l+1,r]中有多少个数,最后单独特判a[l]是否在区间[x,y]内
这个就是经典cdq分治问题,由于数的值域为[1,n],所以用权值线段树(树状数组)可以方便地统计贡献
这种做法和上一种的根本区别在于一个修改操作是覆盖,而另一个是增添和删除,在分治中第二种统计贡献比第一种简单直接很多
然后我就迎来了这题第二次自闭,在晚上10:30写出正确代码后找bug到11:30,直至次日下午5:00才发现问题
只因为一句话的位置:
这是对操作1的处理,注释代码为原位置
如果把d[i]赋值写在后边,就会导致当r==c[l]时,d[i]={0,0,0,0,0,0,0},而我在判断是否应该输出的时候是靠d[i].mk是否为1来判断的
这就导致当r==c[l]发生时,会有修改操作被当成查询操作多输出一个数,自然会WA
这个bug我花了这么长时间没找出来,一个很重要的原因时总在原地打转,算法基本框架检查过很多遍没有问题了,就应该去思考一些细节,尤其是看起来很奇怪很特殊的部分
我倒是看了细节,但是偏偏没有注意到这么奇怪而特殊的一个特判
这个故事也告诉我们有时候睡一觉休息一下bug就自己出来了,不能盲目死磕
代码:
1 #include<iostream>
2 #include<cstdio>
3 #include<algorithm>
4 #include<cstring>
5 #include<cmath>
6 using namespace std;
7 int rd(){int z=0,mk=1; char ch=getchar();
8 while(ch<'0'||ch>'9'){if(ch=='-')mk=-1; ch=getchar();}
9 while(ch>='0'&&ch<='9'){z=(z<<3)+(z<<1)+ch-'0'; ch=getchar();}
10 return z*mk;
11 }
12 struct nds{int mk,x,y,z,id,ans;}a[810000]; int atp=0;
13 int n,m;
14 int b[210000],c[210000];
15 nds q[810000];
16 int v[810000];
17 int ans[210000];
18 nds d[210000];
19 inline int lbt(int x){ return x&-x;}
20 void mdf(int x,int y){ for(;x<=n;x+=lbt(x)) v[x]+=y;}
21 int qry(int x){ int bwl=0; for(;x;x-=lbt(x)) bwl+=v[x]; return bwl;}
22 void cdq(int x,int y,int l,int r){
23 if(x>=y) return ;
24 if(l>r) return ;
25 if(l==r){
26 for(int i=x;i<=y;++i){
27 if(!a[i].mk) mdf(a[i].y,a[i].z);
28 else a[i].ans+=qry(a[i].z)-qry(a[i].y-1);
29 }
30 for(int i=x;i<=y;++i)if(!a[i].mk) mdf(a[i].y,-a[i].z);
31 return ;
32 }
33 int md=(l+r)>>1;
34 int cnt1=0;
35 for(int i=x;i<=y;++i){
36 if(a[i].x<=md) ++cnt1;
37 if(!a[i].mk && a[i].x<=md) mdf(a[i].y,a[i].z);
38 if(a[i].mk && a[i].x>md) a[i].ans+=qry(a[i].z)-qry(a[i].y-1);
39 }
40 for(int i=x;i<=y;++i)if(!a[i].mk && a[i].x<=md)
41 mdf(a[i].y,-a[i].z);
42 int hd1=x,hd2=x+cnt1;
43 for(int i=x;i<=y;++i){
44 if(a[i].x<=md) q[hd1++]=a[i];
45 else q[hd2++]=a[i];
46 }
47 for(int i=x;i<=y;++i) a[i]=q[i];
48 cdq(x,hd1-1,l,md),cdq(hd1,y,md+1,r);
49 }
50 void prvs(){
51 atp=0;
52 for(int i=1;i<=m;++i) ans[i]=0;
53 }
54 int main(){
55 //freopen("ddd.in","r",stdin);
56 cin>>n>>m;
57 prvs();
58 for(int i=1;i<=n;++i) c[i]=rd();
59 c[0]=0,c[n+1]=0;
60 for(int i=1;i<=n;++i){
61 if(c[i]!=c[i-1]) a[++atp]=(nds){0,i,c[i],1,-1,0};
62 b[i]=c[i];
63 }
64 int mk,l,r,ql,qr;
65 for(int i=1;i<=m;++i){
66 mk=rd();
67 if(mk==1){
68 l=rd(),r=rd();
69 d[i]=(nds){mk,l,r,0,0,i};
70 if(r==c[l]) continue;
71 if(c[l]!=c[l-1]) a[++atp]=(nds){0,l,c[l],-1,i,0};
72 if(r!=c[l-1]) a[++atp]=(nds){0,l,r,1,i,0};
73 if(l<n){
74 if(r==c[l+1]) a[++atp]=(nds){0,l+1,c[l+1],-1,i,0};
75 if(c[l]==c[l+1]) a[++atp]=(nds){0,l+1,c[l+1],1,i,0};
76 }
77 c[l]=r;
78 //d[i]=(nds){mk,l,r,0,0,i}; Attention!!!
79 }
80 else{
81 l=rd(),r=rd(),ql=rd(),qr=rd();
82 a[++atp]=(nds){1,r,ql,qr,i,0};
83 a[++atp]=(nds){-1,l,ql,qr,i,0};
84 d[i]=(nds){mk,l,r,ql,qr,i};
85 }
86 }
87 cdq(1,atp,1,n);
88 for(int i=1;i<=atp;++i)if(a[i].mk) ans[a[i].id]+=a[i].mk*a[i].ans;
89 for(int i=1;i<=m;++i){
90 if(d[i].mk==1) b[d[i].x]=d[i].y;
91 else printf("%d\n",ans[i]+(b[d[i].x]>=d[i].z && b[d[i].x]<=d[i].id));
92 }
93 return 0;
94 }
来源:oschina
链接:https://my.oschina.net/u/4261771/blog/3399849