zoukankan      html  css  js  c++  java
  • 分块

    ACM竞赛中数据结构题目心得:分块【With HDU4366】转自sweet kang (http://www.cnblogs.com/sweetsc/archive/2012/08/15/2639395.html)

    写的不错适合入门分块。

    我在ACM竞赛中,一般负责决定队伍的下限:水题能不能清理出来……其他太高深的题目,我表示我还是挺无脑的,一般都不老会的……只有数据结构类题还是挺得心应手的……而个人心得体会最深刻的还是无脑的方法:个人称为根号N法……

    主要思想就是将待操作的长度为N的区间分成大小为sqrt(N)的块,然后实现各种操作……

    一些常用定义:

    MAGIC:定义一个块的大小,如字面意思,一个莫名其妙的数字……

    于是,我们把一段长度为N的区间,分成了若干长度为 MAGIC 的区间:[0,magic),[magic, 2magic)....

    于是易得,i / MAGIC 就是点 i 所在块的编号,若 i % MAGIC == 0,则证明由点 i 开始是一个新区间

    一般来讲,我们在预处理和修改的时候,维护两个信息,一个是序列,另一个是块

    应用1:

    静态RMQ问题,求一个长度为N的序列中区间 l,r 中的最大/小值

    在读入序列的时候预处理得到每个块里的最大值

    对一段区间l,r进行查询的时候,将其分成若干段 [l , magic * i) , [magic * i , magic * (i + 1)) ... [magic * j .. r],取最大值

    其中左右两端需要暴力,然后中间的 [magic * i , magic * (i + 1)) ... 等区间,直接调用预处理的结果

    预处理O(N),每个查询O(sqrt(N))

    int num[11111];
    int max[111];
    int MAGIC = 111;
    int n;
     
    void init() {
        for (int i = 0; i <n; i++) {
            if (i % MAGIC == 0 || num[i] > max[i / MAGIC]) {
                            max[i / MAGIC] = num[i];
            }
        }
    }
     
    int query(int l,int r) {
        int ret = num[l];
        for (int j = l; j <= r;) {
            if (j % MAGIC == 0 && j + MAGIC - 1 <= r) {
                if (max[j / MAGIC] > ret) ret = max[j / MAGIC];
                j += MAGIC;
            } else {
                if (num[j] > ret) ret = num[j];
                j += 1;
            }
        }
        return ret;
    }

    应用2:动态RMQ问题,在应用1的基础上增加条件:可以修改某点的值

    修正某点的值,然后维护该点所在的块,复杂度O(sqrt(N))

    void update(int x,int delta) {
        num[x] = delta;
        int l = x / MAGIC * MAGIC;
        int r = l + MAGIC;
        for (int i = l; i < r; i++) {
            if (i % MAGIC == 0 || num[i] > max[i / MAGIC]) max[i / MAGIC] = num[i];
        }
    }

    其他应用:区间求和(静态,动态),区间染色,等等等等……To Be continued……如果题目时间卡的不是太紧,都可以用sqrt(N)大法水一水

    精通线段树的同志们应该更有心得,这个方法相当于一层分根号N叉的一个线段树……似乎这个方法没有什么意义,不过这个方法各种意义上都是更加无脑,思维复杂度,编码复杂度都很低,而且随着现在机器越来越好,根号N的方法很难被卡住,还是值得一试的……

    下面看看今天多校的题目:http://acm.hdu.edu.cn/showproblem.php?pid=4366

    题意是给一个树,树上每个节点都有两个属性:忠诚度和能力,给出若干查询,求每个子树中能力 > 树根能力的点中,忠诚度最高的那个

    首先容易想到DFS一趟,把问题转化为区间查询问题,相当于查找一段区间[L,R]里,能力 > X 的点中,忠诚度最高的点

    于是决定用根号N法水一水:把区间分块:[0,MAGIC), [MAGIC, 2MAGIC....),并按照块内的节点能力值排序

    然后应用个简单DP思想,O(MAGIC) 推出从块内每个点开始到块末尾的最大忠诚度是多少,这样一个块的信息就初始化完成了

    查询的时候,如果待查询区间[l,R]和块相交,则直接暴力,如果[l,R]完全包含一个块,则在块里二分能力值X,然后返回块内能力值 > X 的最大忠诚度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    #include <cstdio>
    #include <vector>
    #include <map>
    #include <algorithm>
     
    using namespace std;
    typedef long long Long;
    const int MAGIC = 250;
     
    struct staff {
        int loyalty;
        int ability;
    };
     
    bool operator < (staff a,staff b) {
        return a.ability < b.ability;
    }
     
    vector<int> adj[55555];
    staff arr[55555];
    int pos[55555];
    map<int,int> rev;
    int tot;
    staff list[55555];
    staff sorted[55555];
    int maxl[55555];
    int size[55555];
    int n,q;
     
    int dfs(int now) {
        pos[now] = tot;
        list[tot] = sorted[tot] = arr[now];
        tot ++;
        int ret = 1;
        for (int i = 0; i < adj[now].size(); i++) {
            ret += dfs(adj[now][i]);
        }
        return size[pos[now]] = ret;
    }
     
    int work(int l,int r,int val) {<br>// 在块l,r内返回能力值 > val 的最大忠诚<br>// 二分区间端点判定
        if (sorted[r].ability <= val) return -1;
        if (sorted[l].ability > val) return maxl[l];
        while (l + 1 < r) {
            int mid = (l + r) >> 1;
            if (sorted[mid].ability > val) r = mid; else l = mid;
        }
        return maxl[r];
    }
     
    int main() {
        int nn;
        scanf("%d",&nn);
        while (nn--) {
            scanf("%d%d",&n,&q);
            for (int i = 0; i < n; i++) {
                adj[i].clear();
                arr[i].loyalty = arr[i].ability = -1;
                sorted[i] = list[i] = arr[i];
            }
            memset(maxl,0,sizeof(maxl));
            memset(size,0,sizeof(size));
            memset(pos,0,sizeof(pos));
            rev.clear();
            rev[-1] = -1;<br>// 以上是初始化
            for (int i = 1; i < n; i++) {
                int fa,l,a;
                scanf("%d%d%d",&fa,&l,&a);
                adj[fa].push_back(i);<br>// 由于保证忠诚度不同,为了操作方便,map忠诚度到人
                rev[arr[i].loyalty = l] = i;
                arr[i].ability = a;
            }
            tot = 0;
            dfs(0);<br>// 以上是构图DFS
            for (int i = 0; i < n; i += MAGIC) {
                int j = i + MAGIC;
                if (j > n) break;<br>// 块内排序
                sort(sorted + i, sorted + j);<br>// DP构造忠诚度
                maxl[j - 1] = sorted[j - 1].loyalty;
                for (int k = j - 2; k >= i; k--) {
                    maxl[k] = maxl[k + 1] > sorted[k].loyalty ? maxl[k + 1] : sorted[k].loyalty;
                }
            }
            while (q--) {
                int st; scanf("%d",&st);
                int val = arr[st].ability;
                st = pos[st];
                int ed = st + size[st] - 1;
                int ans = -1;
                for (int i = st; i <= ed;) {<br>// 二分块
                    if (i % MAGIC == 0 && i + MAGIC - 1 <= ed) {
                        int tmp = work(i, i + MAGIC - 1, val);
                        if (tmp > ans) ans = tmp;
                        i += MAGIC;
                    else {<br>// 暴力搞
                        if (list[i].ability > val && list[i].loyalty > ans) ans = list[i].loyalty;
                        i ++;
                    }
                }
                printf("%d ",rev[ans]);
            }1
        }
        return 0;
    }

    今天尝到甜头之后,试图把POJ2104也根号N大法了

    题意是给一个序列,查询区间内的第K大值

    我们同样分块,预处理,把块内元素排序。然后对每个查询,二分第K大值,设为X,对X,统计区间内有多少数小于X,如果区间包含块则二分,否则暴力。

    这样复杂度为二分log(x) × max(块数 × log(MAGIC) + MAGIC × 2),经无数次调换MAGIC,以及应用了WS读入法,也过不了……

    于是,咱们将分块方法优化一下,也弄点层次出来:设第 i 层块大小为 1 << i,初始化同理。

    每次查询的时候,试图走最大的 2 的幂次的步长……

    直接上代码似乎更容易明白:

    #include <stdio.h>
    #include <algorithm>
    
    using namespace std;
    
    const int MAGIC = 18;
    int n,m;
    int arr[111111];
    int sorted[20][111111];
    
    // 找出第ind层,区间为l,r的块中有多少数 < val int work(int ind,int l,int r,int val) { int *sorted = ::sorted[ind]; if (sorted[l] >= val) return 0; if (sorted[r] < val) return r - l + 1; int st = l; while (l + 1 < r) { int mid = (l + r) >> 1; if (sorted[mid] < val) l = mid; else r = mid; } return r - st; } int main() { scanf("%d%d",&n,&m); for (int i = 0; i < n; i++) { scanf("%d",arr + i); } for (int j = 0; j < MAGIC; j++) { for (int i = 0; i < n; i++) { sorted[j][i] = arr[i]; } }
    // 预处理每层大小为 2,4,8,16... 的块 for (int j = 1; j < MAGIC; j++) { int step = 1 << j; for (int i = 0; i + step - 1 < n; i += step) { sort(sorted[j] + i, sorted[j] + i + step); } } while (m --) { int l,r,k; scanf("%d%d%d",&l,&r,&k); l --; r --; int ll = -1e9 - 1; int rr = 1e9 + 1; while (ll + 1 < rr) { int rank = 0; int mid = (ll + rr) >> 1; for (int i = l; i <= r;) { for (int j = MAGIC; j >= 0; j--) {
                            // 选择最大的2的幂次的步长,调用块里对应的信息 int step = 1 << j; if (i % step == 0 && i + step - 1 <= r) { rank += work(j,i, i + step - 1,mid); i += step; break; } } } if (rank < k) ll = mid; else rr = mid; } printf("%d ",ll); } return 0; }

    这个复杂度的话,外层二分,log(N),每次会分log(N)块,块内二分Log(N),总复杂度Log(N)^3

    在一个好的Blog上见过句话:定义若干正则集合,并将他们组织成某种合适的结构,而查找算法就是要把查找的结果表示成若干个正则集合的划分,进而在每个正则集合中通过枚举的方式实现查找。可见,分块,线段树等等都是这个思想

  • 相关阅读:
    Mysql登录错误:ERROR 1045 (28000): Plugin caching_sha2_password could not be loaded
    Docker配置LNMP环境
    Docker安装mysqli扩展和gd扩展
    Docker常用命令
    Ubuntu常用命令
    单例模式的优缺点和使用场景
    ABP 多租户数据共享
    ABP Core 后台Angular+Ng-Zorro 图片上传
    ERROR Error: If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions.
    AbpCore 执行迁移文件生成数据库报错 Could not find root folder of the web project!
  • 原文地址:https://www.cnblogs.com/forgot93/p/4478442.html
Copyright © 2011-2022 走看看