整体二分初步


  整体二分是个很神的东西,它可以把许多复杂的数据结构题化简。它的精髓在于巧妙地利用了离线的特点,把所有的修改、询问操作整体把握。

  先说说第k大数吧,这种问题是整体二分的标志性题目,什么划分树啊,主席树啊,树套树啊见了整体二分都得自叹不如。首先对于一次询问来说我们可以二分答案,然后通过验证比答案大的数有多少个来不断地缩小答案范围直至得到一个准确的答案。而对于多个询问我们同样可以这么做,只不过对每一个询问我们都需要判定一下,以决定它被划分到哪一个答案的区间里。这个判定过程就是通过比较比二分的mid大的数的个数和k。同时我们看到,如果比二分的mid大的数的个数小于k了,我们是要去寻找小的答案,那么这些比mid大的数在以后的递归里始终会对答案有贡献,所以我们没必要去做重复的工作,只需要把这些数的个数累积到贡献里,以后递归的时候就不用考虑这些数了。具体地,我们把数列里的数也和询问一起递归,这样这些数也会被分到属于的答案区间里,并且只对相应区间里的询问有影响。如果有修改呢,我们把修改看成一个数就好了,一样可以随着递归不断地被划分下去。

  我们看到,整体二分的过程实质上是个按照数值来划分操作序列的过程,于是我们的复杂度也就和操作序列的长度线性相关,那么我们在中间维护一些信息的时候,就一定不能有何数列长线性相关的东西,否则会破坏其时间复杂度,具体的复杂度证明请见2013年集训队XHR论文。

  贴个典型性的代码吧:

  带修改区间第k小数

  

View Code
  1 #include<iostream>
  2 #include<cstdio>
  3 #include<algorithm>
  4 #include<cmath>
  5 #include<cstring>
  6 #define maxn 220000
  7 #define inf 1000000000
  8 
  9 using namespace std;
 10 struct query
 11 {
 12     int x,y,k,s,tp,cur;
 13 }q[maxn],q1[maxn],q2[maxn];
 14 int a[maxn],ans[maxn],tmp[maxn],t[maxn];
 15 int n,m,num,cnt;
 16 
 17 void add(int x,int y)
 18 {
 19     for (int i=x;i<=n;i+=(i&-i)) t[i]+=y;
 20 }
 21 
 22 int ask(int x)
 23 {
 24     int tmp=0;
 25     for (int i=x;i>0;i-=(i&-i)) tmp+=t[i];
 26     return tmp;
 27 }
 28 
 29 void divide(int head,int tail,int l,int r)
 30 {
 31     //cout<<head<<' '<<tail<<' '<<l<<' '<<r<<endl;
 32     if (head>tail) return ;
 33     if (l==r) 
 34     {
 35         for (int i=head;i<=tail;i++)
 36             if (q[i].tp==3) ans[q[i].s]=l;//,cout<<l<<endl;
 37         return ;
 38     }
 39     int mid=(l+r)>>1;
 40     for (int i=head;i<=tail;i++)
 41     {
 42         if (q[i].tp==1&&q[i].y<=mid) add(q[i].x,1);
 43         else
 44         if (q[i].tp==2&&q[i].y<=mid) add(q[i].x,-1);
 45         else
 46         if (q[i].tp==3) tmp[i]=ask(q[i].y)-ask(q[i].x-1);
 47     }
 48     for (int i=head;i<=tail;i++)
 49     {
 50         if (q[i].tp==1&&q[i].y<=mid) add(q[i].x,-1);
 51         else
 52         if (q[i].tp==2&&q[i].y<=mid) add(q[i].x,1);
 53     }
 54     int l1=0,l2=0;
 55     for (int i=head;i<=tail;i++)
 56         if (q[i].tp==3)
 57         {
 58             if (q[i].cur+tmp[i]>q[i].k-1)//q[i].cur+tmp[i]表示累积了多少个数
 59                 q1[++l1]=q[i];
 60             else
 61             {
 62                 q[i].cur+=tmp[i];
 63                 q2[++l2]=q[i];
 64             }
 65         }
 66         else
 67         {
 68             if (q[i].y<=mid) q1[++l1]=q[i];
 69             else q2[++l2]=q[i];
 70         }
 71     for (int i=1;i<=l1;i++) q[head+i-1]=q1[i];
 72     for (int i=1;i<=l2;i++) q[head+l1+i-1]=q2[i];
 73     divide(head,head+l1-1,l,mid);
 74     divide(head+l1,tail,mid+1,r);
 75 }    
 76 
 77 int main()
 78 {
 79     //freopen("ranking.in","r",stdin);
 80     //freopen("ranking.out","w",stdout);
 81     scanf("%d%d",&n,&m);
 82     for (int i=1;i<=n;i++) 
 83     {
 84         scanf("%d",&a[i]);
 85         q[++num].x=i; q[num].y=a[i];
 86         q[num].tp=1; q[num].s=0;
 87     }
 88     char sign;
 89     int x,y,z;
 90     for (int i=1;i<=m;i++)
 91     {
 92         scanf("\n%c",&sign);
 93         if (sign=='Q')
 94         {
 95             scanf("%d%d%d",&x,&y,&z);
 96             q[++num].x=x,q[num].y=y,q[num].k=z;
 97             q[num].tp=3; q[num].s=++cnt;
 98         }
 99         else
100         {
101             scanf("%d%d",&x,&y);
102             q[++num].x=x; q[num].y=a[x]; 
103             q[num].tp=2; q[num].s=0;
104             q[++num].x=x; q[num].y=y;
105             q[num].tp=1; q[num].s=0;
106             a[x]=y;
107         }
108     }
109     divide(1,num,0,inf);
110     for (int i=1;i<=cnt;i++)
111         printf("%d\n",ans[i]);
112     return 0;
113 }

  

  树状数组什么的我最喜欢了,又短又快!

  代码里有许多可以缩起来写的地方,我没改,目测这样也比主席树短不少......


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM