• 分块


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

  • 相关阅读:
    python3 解决tcp黏包方法一
    python3 udp不黏包但丢数据
    python3 类的单例模式
    python3 day07 练习题
    python3 tcp黏包情况二
    python3 tcp黏包情况一
    python3 subprocess模块
    python3 UDP协议下的socket
    python3 TCP协议下的socket
    python3 socket网络通信的整个流程
  • 原文地址:https://www.cnblogs.com/forgot93/p/4478442.html
Copyright © 2020-2023  润新知