• 整体二分


    样例跑下,对于数列
    1 3 4 5 6 7 10 12
    求第3小的
    对于整个数列,最小值为1,最大值为12,中间值为6
    我们将小于等于6的数字找出来,发现有5个
    说明我们二分枚举的答案太大了,所以小于它的数字过多。
    于是将小于6的数字变成一个数列
    1 3 4 5 6
    再取1与6中间值为3
    发现小于等于3的数字只有2个,达不到我们的要求
    说明答案大于3.于是应该在数列
    4 5 6
    中找第1小的
    此时再取4与6的中间值为5
    发现小于等于5的有2个,大于1,说明值又取大了
    于是将小于等于5的数字找出来
    4 5
    再取4和5的中间值为4
    小于等于4的只有1个。于是数字序列只有4.答案区间也只有[4,4]
    于是最后结果为4

    如果是有多个询问,注意加入值的操作在前面,询问在后面。
    经过二分后,在每个区间内部,仍然是加值操作在前,询问操作在后面

    一般来讲我们会把每个有权值的位置放到一个队列里,然后所有操作也放在这个队列里,但是放在权值之后。

    然后我们二分一个值,将所有这个值下仍能合法的询问丢到右区间处理 (也就是它们的二分区间肯定都变成了 [mid+1,r] ) ,同时将大于等于 mid 的权值和修改权值丢到右边去,因为它们会对且仅对右区间的询问产生贡献。

    左区间同理。

    当我们的二分区间 l==r 时,更新所有二分区间为这个的询问的答案。

    大致就是一个函数 Solve(L,R,l,r) ,代表我们现在处理的是 [L,R] 的操作或权值序列,它们对应的二分区间都是 [l,r] 。每次求出有 len1 个操作和权值要丢到左区间,len2 个丢到右区间,那我们就拿出这些操作并重新摆放位置,确定那 len1 个操作都在 [L,L+len1−1] 这段区间,且按照操作的时间顺序摆放,另外 len2 个操作同理。

    那么我们的递归处理就是 Solve(L,L+len1−1,l,mid) 与 Solve(L+len,R,mid+1,r) 了。

    POJ2104 第K小数

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N = 100010, INF = 1e9;
    struct rec {int op, x, y, z;} q[2 * N], lq[2 * N], rq[2 * N];
    int n, m, t, c[N], ans[N];
    
    int ask(int x) {
        int y = 0;
        for (; x; x -= x & -x) y += c[x];
        return y;
    }
    
    void change(int x, int y) {
        for (; x <= n; x += x & -x) c[x] += y;
    }
    
    void solve(int lval, int rval, int st, int ed) 
    {
        if (st > ed) return;
        if (lval == rval) 
        //值域达到边界 
    	{
            for (int i = st; i <= ed; i++)
                if (q[i].op > 0) //如果是一次询问操作 
    			   ans[q[i].op] = lval;
            return;
        }
        int mid = (lval + rval) >> 1;
        //!!!二分枚举一个值出来 
        //将小于等于mid的数字放在lq,大于的放在rq 
        int lt = 0, rt = 0;
        for (int i = st; i <= ed; i++) 
    	{
            if (q[i].op == 0) { //如果是一次赋值操作 
                if (q[i].y <= mid) //如果要所赋的值小于mid 
    			     change(q[i].x, 1), lq[++lt] = q[i];
    			     //在第x个位置上加上1 
                else 
    			      rq[++rt] = q[i];
            } 
    		else  //如果是一次询问操作 
    		{ 
                int cnt = ask(q[i].y) - ask(q[i].x - 1);
                //cnt统计在第i个询问中,小于mid的数字有多少个 
                if (cnt >= q[i].z) 
    			    lq[++lt] = q[i];
    			//答案落在左边 
                else 
    			     q[i].z -= cnt, rq[++rt] = q[i];
    			//答案落在右边 
            }
        }
        for (int i = ed; i >= st; i--) { 
    	// 还原树状数组
    	//可以设想下mid的值较大,此时就会在bit中加入一些数字
    	//但事实上查找范围落在左边,于是要清空当前的操作,后面再来加入 
            if (q[i].op == 0 && q[i].y <= mid) change(q[i].x, -1);
        }
        for (int i = 1; i <= lt; i++) q[st + i - 1] = lq[i];
        for (int i = 1; i <= rt; i++) q[st + lt + i - 1] = rq[i];
        solve(lval, mid, st, st + lt - 1);
        solve(mid + 1, rval, st + lt, ed);
    }
    
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; i++) 
    	{
            int val; scanf("%d", &val);
            // a[i]=val,将其看成一个赋值操作 
            q[++t].op = 0, q[t].x = i, q[t].y = val;
        }
        for (int i = 1; i <= m; i++) {
            int l, r, k; scanf("%d%d%d", &l, &r, &k);
            // 询问[l,r]之间第k小的数字 
            q[++t].op = i, q[t].x = l, q[t].y = r, q[t].z = k;
        }
       
        solve(-INF, INF, 1, t);
        for (int i = 1; i <= m; i++) printf("%d
    ", ans[i]);
    }
    

      

     Sol2:可持久化线段树的做法

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N = 100010, INF = 1e9;
    struct SegmentTree {
        int lc, rc; 
    	// 左右子节点编号
        int sum;
    } tree[N * 20];
    int n, m, t, tot, a[N], b[N], root[N];
    
    int build(int l, int r) 
    {
        int p = ++tot; 
    	// 新建一个节点,编号为p,代表当前区间[l,r]
        tree[p].sum = 0;
        if (l == r) return p;
        int mid = (l + r) >> 1;
        tree[p].lc = build(l, mid);
        tree[p].rc = build(mid + 1, r);
        return p;
    }
    
    int insert(int now, int l, int r, int x, int delta) 
    {
        int p = ++tot;
        tree[p] = tree[now];
    	 // 新建一个副本
        if (l == r) {
            tree[p].sum += delta; 
    		// 在副本上修改
            return p;
        }
        int mid = (l + r) >> 1;
        if (x <= mid) 
    	     tree[p].lc = insert(tree[now].lc, l, mid, x, delta);
        else 
    	     tree[p].rc = insert(tree[now].rc, mid + 1, r, x, delta);
        tree[p].sum = tree[tree[p].lc].sum + tree[tree[p].rc].sum;
        return p;
    }
    
    //在p,q两个节点上,值域为[l,r],求第k小数
    int ask(int p, int q, int l, int r, int k) 
    {
        if (l == r) return l;
    	 // 找到答案
        int mid = (l + r) >> 1;
        int lcnt = tree[tree[p].lc].sum - tree[tree[q].lc].sum; 
         // 值在[l,mid]中的数有多少个
        if (k <= lcnt) 
    	     return ask(tree[p].lc, tree[q].lc, l, mid, k);
        else 
    	    return ask(tree[p].rc, tree[q].rc, mid + 1, r, k - lcnt);
    }
    
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
    	 {
            scanf("%d", &a[i]);
            b[++t] = a[i];
        }
        sort(b + 1, b + t + 1);
    	 // 离散化
        t = unique(b + 1, b + t + 1) - (b + 1);
        root[0] = build(1, t); 
    	// 关于离散化后的值域建树
        for (int i = 1; i <= n; i++) 
    	{
            int x = lower_bound(b + 1, b + t + 1, a[i]) - b; 
    		// 离散化后的值
            root[i] = insert(root[i - 1], 1, t, x, 1); 
    		// 值为x的数增加1个
        }        
        for (int i = 1; i <= m; i++) 
    	{
            int l, r, k; scanf("%d%d%d", &l, &r, &k);
            int ans = ask(root[r], root[l - 1], 1, t, k);
            printf("%d
    ", b[ans]); 
    		// 从离散化后的值变回原值
        }
    }
    

      

    zju2112
    给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。对于每一个询问指令,你必须输出正确的回答。

    输入
    第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。分别表示序列的长度和指令的个数。第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。接下来的m行描述每条指令,每行的格式是下面两种格式中的一种。 Q i j k 或者 C i t Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t。

    输出
    对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。

    样例
    输入复制
    5 3
    3 2 1 4 7
    Q 1 4 3
    C 2 6
    Q 2 5 3
    输出复制
    3
    6

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N = 100010, INF = 1e9;
    struct rec {int op, x, y, z;} q[3 * N], lq[3 * N], rq[3 * N];
    int T, n, m, t, p, a[N], c[N], ans[N];
    
    //树状数组
    int ask(int x) {
        int y = 0;
        for (; x; x -= x & -x) y += c[x];
        return y;
    }
    
    void change(int x, int y) {
        for (; x <= n; x += x & -x) c[x] += y;
    }
    
    //lval和rval代表值域,st和ed代表操作序列区间
    void solve(int lval, int rval, int st, int ed) {
        if (st > ed) return;
        if (lval == rval) {
            for (int i = st; i <= ed; i++)
                if (q[i].op > 0) ans[q[i].op] = lval; //询问
            return;
        }
        int mid = (lval + rval) >> 1; 
    	//对值域二分
        int lt = 0, rt = 0; 
    	//左右操作个数
        for (int i = st; i <= ed; i++) 
    	{ //序列区间
            if (q[i].op <= 0) 
    		{ // 代表修改,-1为去掉一个值,0为增加一个值
                if (q[i].y <= mid) 
    			    change(q[i].x, q[i].z), lq[++lt] = q[i]; 
    				//分治操作序列
                else 
    			    rq[++rt] = q[i];
            } else 
    		{ // 是一次询问
                int cnt = ask(q[i].y) - ask(q[i].x - 1); 
    			//下标区间 [li,ri] 中不大于mid的数有多少个,记为 cnt。
                if (cnt >= q[i].z) 
    			    lq[++lt] = q[i]; 
    			//若 ki<=ci,则把该询问加入到序列 lq 中
                else
    			    q[i].z -= cnt, rq[++rt] = q[i];
    				//否则,令 ki-=ci,将其加入到序列 rq 中
            }
        }
        for (int i = ed; i >= st; i--) 
    	{ // 还原树状数组
            if (q[i].op <= 0 && q[i].y <= mid) 
    		     change(q[i].x, -q[i].z);
        }
        for (int i = 1; i <= lt; i++) 
    	     q[st + i - 1] = lq[i]; //把lq和rq拷贝回原操作序列的st--ed位置
        for (int i = 1; i <= rt; i++) 
    	     q[st + lt + i - 1] = rq[i];
        solve(lval, mid, st, st + lt - 1); 
    	//递归求解
        solve(mid + 1, rval, st + lt, ed);
    }
    
    int main() 
    {
       
            cin >> n >> m;
            t = p = 0;
            for (int i = 1; i <= n; i++) 
    		{
                int val; scanf("%d", &val);
                // 等价于在第i个位置上加入一个数val
                q[++t].op = 0, q[t].x = i, q[t].y = val, q[i].z = 1;
                a[i] = val;
            }
            for (int i = 1; i <= m; i++) 
    		{
                char op[2]; scanf("%s", op);
                if (op[0] == 'Q') 
    			{
                    int l, r, k; scanf("%d%d%d", &l, &r, &k);
                    // 记录一次询问
                    q[++t].op = ++p, q[t].x = l, q[t].y = r, q[t].z = k;
                } 
    			else 
    			{
                    int x, y; scanf("%d%d", &x, &y);
                    // 去掉原来的数a[x]
                    q[++t].op = -1, q[t].x = x, q[t].y = a[x], q[t].z = -1;
                    // 在第x个位置上加入一个新的数y
                    q[++t].op = 0, q[t].x = x, q[t].y = y, q[t].z = 1;
                    a[x] = y;
                }
            }
            // 基于值域对t=n+m个操作进行整体分治
            solve(0, INF, 1, t);
            for (int i = 1; i <= p; i++)
                printf("%d
    ", ans[i]);
        
    }
    

      

    #include<cstdio>
    #define maxn 30010
    #define INF 1000000000
    #define lowbit(i) (i&-i)
    using namespace std;
    int n,m,a[maxn],cnt,tmp[maxn];
    int ans[maxn],tr[maxn];
    char cmd[10];
    bool flag[maxn];
    struct query{
    int x,y,k,c,o;
    }q[maxn],q1[maxn],q2[maxn];
    void add(int pos,int val)
    {
    for(int i=pos;i<=n;i+=lowbit(i)){
    tr[i]+=val;
    }
    }
    int ask(int pos)
    {
    int ret=0;
    for(int i=pos;i>0;i-=lowbit(i)){
    ret+=tr[i];
    }
    return ret;
    }
    void devide(int head,int tail,int l,int r)
    {
    if(head>tail) return;
    if(l==r)
    {
    for(int i=head;i<=tail;i++)
    {
    if(q[i].c==3)
    {
    ans[q[i].o]=l;
    }
    }
    return;
    }
    int mid=(l+r)>>1;
    for(int i=head;i<=tail;i++)
    {
    if(q[i].c==1&&q[i].y<=mid) add(q[i].x,1);
    if(q[i].c==2&&q[i].y<=mid) add(q[i].x,-1);
    if(q[i].c==3) tmp[i]=ask(q[i].y)-ask(q[i].x-1);
    }
    for(int i=head;i<=tail;i++)
    {
    if(q[i].c==1&&q[i].y<=mid)
    add(q[i].x,-1);
    if(q[i].c==2&&q[i].y<=mid)
    add(q[i].x,1);
    }
    int l1=0,l2=0;
    for(int i=head;i<=tail;i++)
    {
    if(q[i].c==3) //如果是询问操作的话
    {
    if(tmp[i]>=q[i].k) //答案落在左区间
    {
    q1[++l1]=q[i];
    }
    else
    {
    q[i].k-=tmp[i];
    q2[++l2]=q[i];
    }
    }
    else //如果是赋值操作的话
    {
    if(q[i].y<=mid)
    {
    q1[++l1]=q[i];
    }
    else
    {
    q2[++l2]=q[i];
    }
    }
    }
    for(int i=1;i<=l1;i++)
    q[head+i-1]=q1[i];
    for(int i=1;i<=l2;i++)
    q[head+l1+i-1]=q2[i];
    devide(head,head+l1-1,l,mid);
    devide(head+l1,tail,mid+1,r);
    return;
    }
    int main()
    {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
    scanf("%d",&a[i]);
    q[++cnt]=(query){i,a[i],0,1,0};
    }
    for(int i=1;i<=m;i++)
    {
    scanf("%s",cmd);
    if(cmd[0]=='Q') //询问操作
    {
    int x,y,k;
    scanf("%d%d%d",&x,&y,&k);
    q[++cnt]=(query){x,y,k,3,i};
    //区间[x,y]第k大,3代表询问操作,i代表第i个询问
    flag[i]=1;
    }else{
    int x,y;
    scanf("%d%d",&x,&y);
    q[++cnt]=(query){x,a[x],0,2,0};
    q[++cnt]=(query){x,y,0,1,0};
    a[x]=y; //!
    }
    }
    devide(1,cnt,0,INF);
    for(int i=1;i<=m;i++){
    if(flag[i]) printf("%d
    ",ans[i]);
    }
    }
    

      

     整体二分习 题表

    PKU2104 K-th Number
    BZOJ2738 矩阵乘法
    BZOJ2527 [Poi2011]Meteors
    BZOJ3110 [Zjoi2013]K大数查询
    BZOJ4009 [HNOI2015]接水果

    还要再研究的:

    https://www.cnblogs.com/AKMer/category/1397613.html

  • 相关阅读:
    jquery流行的插件收集
    简单遮罩层
    生成任意位数随机验证码
    30个图片浏览插件收集
    [转载]12个jQuery Lightbox效果插件
    jqzoom图片放大镜效果
    23个超流行的jQuery相册插件收集
    在suse上创建UDEV Rules For RAC OCR And Voting Devices
    /etc/fstab文件中的一些参数
    Oracle提供的自治事务记录日志的方法
  • 原文地址:https://www.cnblogs.com/cutemush/p/14229025.html
Copyright © 2020-2023  润新知