zoukankan      html  css  js  c++  java
  • 2018 ACM南京网络赛H题Set解题报告

    题目描述

    给定(n)个数$a_i$,起初第(i)个数在第(i)个集合。有三种操作(共(m)次):

    1 $u$ $v$ 将第$u$个数和第$v$个数所在集合合并

    2 $u$ 将第$u$个数所在集合所有数加1

    3 $u$ $k$ $x$ 问$u$所在集合有多少个数模$2^k$余$x$。

    数据范围:(n,m le 500000,a_i le 10^9, 0 le k le 30)。

    简要题解

    显然此题可以用set加启发式合并在(O(n log ^2 n))时间复杂度解决本题,但此题时限1.5s,必须使用一个log的做法。事实上,这是一道十分套路的Trie树题目。

    首先用并查集维护连通性。

    下面先考虑操作3,这相当于询问低$k$位二进制固定时集合中元素个数,可以用Trie树,维护一个子树中终结结点有多少个即可。

    对于加1操作,可以在Trie树上打标记,类似线段树进行pushDown标记下传。此题pushDown函数很新颖(之前没写过这样的pushDown),详见代码。(队友指出,此题也可以暴力更新Trie树,至多交换(log 10^9)个结点;不过如果每次加的数不是1的话就必须打标记了。)

    对于合并操作,类似线段树合并。由于初始时(n)个数共需要(O(n log 10^9))个结点,而花费O(1)的时间会将总结点数减1,故Trie树合并的总时间复杂度也为(O(n log 10^9))。关于线段树合并,可以做这道入门题练手:Codeforces Gym 101194G(2016EC Final)

    总时间复杂度(O((n+q) log 10^9))。

    注意事项

    此题空间复杂度(O(n log 10^9))。如果Trie树合并使用新开的结点,每个结构体16B,将需要576MB,这会MLE。考虑Trie树合并时不新开结点,可以将空间降至288MB。

    完整代码

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<vector>
     4 #define DEPTH 30
     5 using namespace std;
     6 struct Trie{
     7     int size;
     8     int next[2];
     9     int tag;
    10 }trie[20000001];
    11 int cnt;
    12 int newTrie(){
    13     memset(&trie[++cnt], 0, sizeof(Trie));
    14     return cnt;
    15 }
    16 inline void pushDown(int i){
    17     int &t = trie[i].tag;
    18     int &l = trie[i].next[0], &r = trie[i].next[1];
    19     if (t){
    20         if (t & 1){ swap(l, r); trie[l].tag++; }
    21         if (t >= 2){ trie[l].tag += t / 2; trie[r].tag += t / 2; }
    22         t = 0;
    23     }
    24 }
    25 inline void pushUp(int i){
    26     trie[i].size = trie[trie[i].next[0]].size + trie[trie[i].next[1]].size;
    27 }
    28 void insert(int i, int depth, int x)
    29 {
    30     if (!depth){ trie[i].size++; return; }
    31     pushDown(i);
    32     int &pos = trie[i].next[x & 1];
    33     if (!pos)pos = newTrie();
    34     insert(pos, depth - 1, x >> 1);
    35     pushUp(i);
    36 }
    37 void merge(int& i, int j, int k, int depth)
    38 {
    39     if (j&&k){
    40         i = j;
    41         if (!depth){
    42             trie[i].size += trie[k].size;
    43             return;
    44         }
    45         pushDown(j); pushDown(k);
    46         for (int c = 0; c < 2; c++)
    47             merge(trie[i].next[c], trie[j].next[c], trie[k].next[c], depth - 1);
    48         pushUp(i);
    49     }
    50     else i = j ? j : k;
    51 }
    52 int f[600001], id[600001];
    53 int getFather(int i)
    54 {
    55     if (f[i] == i)return i;
    56     return f[i] = getFather(f[i]);
    57 }
    58 int main()
    59 {
    60     int n, m, x, u, v, k;
    61     scanf("%d%d", &n, &m);
    62     for (int i = 1; i <= n; i++){
    63         scanf("%d", &x);
    64         id[i] = newTrie();
    65         f[i] = i;
    66         insert(id[i], DEPTH, x);
    67     }
    68     while (m--){
    69         scanf("%d%d", &x, &u);
    70         u = getFather(u);
    71         if (x == 1){
    72             scanf("%d", &v);
    73             v = getFather(v);
    74             if (u != v){
    75                 f[u] = v;
    76                 merge(id[v], id[u], id[v], DEPTH);
    77             }
    78         }
    79         else if (x == 2)trie[id[u]].tag++;
    80         else{
    81             scanf("%d%d", &k, &x);
    82             int cur;
    83             for (cur = id[u]; k; k--){
    84                 pushDown(cur);
    85                 cur = trie[cur].next[x & 1];
    86                 if (!cur)break;
    87                 x >>= 1;
    88             }
    89             if (!cur)printf("0
    ");
    90             else printf("%d
    ", trie[cur].size);
    91         }
    92     }
    93 }
  • 相关阅读:
    day19 反射
    Oracle函数整理
    在博客园设置访问人数
    数据库中行转列
    Oracle中数据库与实例的区别
    sql语句的执行顺序
    【地址】ps_cs6安装
    ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务 的解决方法
    人员管理模块密码过期
    相关性配置模块总结
  • 原文地址:https://www.cnblogs.com/zbh2047/p/9572187.html
Copyright © 2011-2022 走看看