ACM 2018 南京网络赛H题Set解题报告
Posted zbh2047
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ACM 2018 南京网络赛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),详见代码。
对于合并操作,类似线段树合并。由于初始时(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 }
以上是关于ACM 2018 南京网络赛H题Set解题报告的主要内容,如果未能解决你的问题,请参考以下文章
2018ACM-ICPC亚洲区域赛南京站I题Magic Potion(网络流)