zoukankan      html  css  js  c++  java
  • 分块算法

    [有一种很常见的题型  就是一个在一个好长的序列中进行 乍一看都是很麻烦的那种 修改和查询。

    通常这种题 都是拿高端的数据结构 轻轻松松优化查询修改复杂度写过去,可是不会怎么办??!

    于是就可以利用分块这种小技巧了。

    这个技能就算是带奇技淫巧的暴力吧,暴力出奇迹。

    简单来说 : 这种思路就是 把序列分成很多个块, 建立块与块之间的联系,然后每次修改就只需要改一个块里的东西。

    如果分成 √N 块,每块有 √N 个 , 那么每次修改 查询 的操作 就都被减到了以前的根号级别, 所以就可以水过好多题

    看下面两道例题,写完就会分块了。

    [HNOI2010]BOUNCE 弹飞绵羊

    原链接 https://www.luogu.org/problem/show?pid=1393

    题的意思是,地上沿一条直线摆了N个弹簧,第i个弹簧弹力值是Ki ,代表这个弹簧会把绵羊弹飞到位置 i+Ki,若不存在 i+Ki则绵羊被弹飞。M次操作,询问操作是给定一个起点x,求小绵羊从x出发被弹几次后被弹飞,修改操作是把第i个弹簧的弹力值修改为k

    N<=200000,M<=100000

    这个题的标程是LCT(link-cut-tree)  (有朝一日我学了会回来发题解的)

    做法是先分块,(求一下每个块的l,r 每个点的belong)

        block=( int)sqrt(N);
        cnt=(N%block)?N/block+1:N/block;
        for(int i=1;i<=cnt;i++)
            l[i]=r[i-1]+1,r[i]=l[i]+block-1;
        r[cnt]=N;
        for(int i=1,j=1;i<=cnt;j++)
        {
            belong[j]=i;
            if(j==r[i])i++;
        }

    然后 算一下 每个弹簧 可以弹到 下一个块的什么位置(还有步数)

     for(int i=N;i>=1;i--)    
        {
            to[i]=i+a[i];
            if(to[i]>r[belong[i]])step[i]=1;
            else step[i]=step[to[i]]+1,to[i]=to[to[i]];
        }

    就维护上面这些,所以每次修改只用改一个块里的东西

    然后询问的时候就是一块一块的跳 累加上步数。

    while(M--)
        {
            int k,x,y;
            read(k);
            if(k==1)    //query
            {
                ans=0;read(x);x++;
                while(x<=N)ans+=step[x],x=to[x];
                printf("%d
    ",ans);
            }
            else        //change
            {
                read(x);read(y);x++;
                a[x]=y;
                for(int i=r[belong[x]];i>=l[belong[x]];i--)
                {
                    to[i]=i+a[i];
                    if(to[i]>r[belong[i]])step[i]=1;
                    else step[i]=step[to[i]]+1,to[i]=to[to[i]];
                }
            }
        }
     1 #include<bits/stdc++.h>
     2 #define MAXN 200001
     3 void read(int& x)
     4 {
     5     x=0;char c;c=getchar();
     6     while(c<'0'||c>'9')c=getchar();
     7     while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
     8 }
     9 int N,M,block,cnt,ans,a[MAXN],l[MAXN],r[MAXN],step[MAXN],to[MAXN],belong[MAXN];
    10 int main()
    11 {
    12     read(N);
    13     for(int i=1;i<=N;i++)read(a[i]);
    14 //initiation
    15     block=( int)sqrt(N);
    16     cnt=(N%block)?N/block+1:N/block;
    17     for(int i=1;i<=cnt;i++)
    18         l[i]=r[i-1]+1,r[i]=l[i]+block-1;
    19     r[cnt]=N;
    20     for(int i=1,j=1;i<=cnt;j++)
    21     {
    22         belong[j]=i;
    23         if(j==r[i])i++;
    24     }
    25     for(int i=N;i>=1;i--)
    26     {
    27         to[i]=i+a[i];
    28         if(to[i]>r[belong[i]])step[i]=1;
    29         else step[i]=step[to[i]]+1,to[i]=to[to[i]];
    30     }
    31 //operation
    32     read(M);
    33     while(M--)
    34     {
    35         int k,x,y;
    36         read(k);
    37         if(k==1)    //query
    38         {
    39             ans=0;read(x);x++;
    40             while(x<=N)ans+=step[x],x=to[x];
    41             printf("%d
    ",ans);
    42         }
    43         else        //change
    44         {
    45             read(x);read(y);x++;
    46             a[x]=y;
    47             for(int i=r[belong[x]];i>=l[belong[x]];i--)
    48             {
    49                 to[i]=i+a[i];
    50                 if(to[i]>r[belong[i]])step[i]=1;
    51                 else step[i]=step[to[i]]+1,to[i]=to[to[i]];
    52             }
    53         }
    54     }
    55     return 0;
    56 }
    完整代码

    [CQOI2011]动态逆序对

    原链接 https://www.luogu.org/problem/show?pid=3157 

    题意是有一个长度为N的序列,M次操作,每次删一个数并求逆序对个数

    N<=100000,M<=50000

    正经做法里有 线段树+BST的 CDQ分治的 还有主席树?

    分块做法 : 每个块都存在一个树状数组里。(不懂的话去写一个树状数组求逆序对的模板=、=)

    先算一下修改之前的逆序对个数,

    减去其它块的贡献(贡献直接拿树状数组查询)   自己块里的贡献就直接直接遍历一遍。

    这个题分100块最优 。 拿复杂度式子求最小值可以推出来。  <-推分的块数一定别忘了 , 能快很多

    就贴个核心操作部分的代码吧,很好理解。

    //    operation
        while(M--)
        {
            int x;cin>>x;
        //    calc
            for(int i=1;i<belong[x];i++)
                ans-=siz[i]-query(c[belong[x]],a[x]);
            for(int i=l[belong[x]];i<x;i++)
                if(a[i]>a[x]&&a[i])ans--;
            for(int i=x+1;i<=r[belong[x]];i++)
                if(a[x]>a[i]&&a[i])ans--;
            for(int i=belong[x]+1;i<=block;i++)
                ans-=query(c[belong[x]],a[x]-1); 
            cout<<ans<<" ";
        //    update
            add(c[belong[x]],a[x],-1);a[x]=0;siz[belong[x]]--;           
        }

    千言万语汇成一句话:

    天下武功出暴力

  • 相关阅读:
    CSS系列:长度单位&字体大小的关系em rem px
    CSS兼容性
    html5+css3
    将url的查询参数解析成字典对象
    SQL阻止保存要求重新创建表的更改 在哪里设置
    Jquery&JS简单选项卡
    块级&行内(内联)元素
    时间
    PHP 二维数组根据某个字段排序
    php 操作数组 (合并,拆分,追加,查找,删除等)
  • 原文地址:https://www.cnblogs.com/Elfish/p/7570167.html
Copyright © 2011-2022 走看看