标签:
题目链接 : http://acm.hdu.edu.cn/showproblem.php?pid=5156
题意 :
给一颗编号为1-n的以1为根的树, 已知有m个颜色的礼物分布在某些节点上(同一节点可以有多个),
问 : 对于编号从1-n的节点, 每一个节点对应子树上有多少颜色不同的礼物.
思路 :
一开始的想法是DFS记录节点序列, 再开vector记录每个节点上挂的礼物(同一节点上对颜色去重), 用树状数组统计一个区间内不同颜色的种类
但是由于记录位置的数组同样要开到二维, 超过了限制, 于是也开成vector, 果断超时
后来还是看discuss里边sxbk同学的代码才知道更好的解法
DFS记录序列不是节点序列, 而是将所有节点的全部颜色(已去重)都记录在序列内, 这样位置记录的数组可以只用一维
这道题收获挺大的, 对DFS序的思想理解深入了, 同时学习了 统计一个区间内有多少不同的数 这个问题的解法
题目代码 :
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <vector> 5 6 using namespace std; 7 8 const int MAXN = 5e4+10; 9 const int MAXM = 5e5+10; 10 11 vector<int> edge[MAXN]; 12 vector<int> gift[MAXN]; 13 int seq[2*MAXM]; 14 int st[MAXN]; 15 int ed[MAXN]; 16 int s[2*MAXM]; 17 int mp[2*MAXM]; 18 int ans[MAXN]; 19 int pos[MAXN]; 20 int cnt, n, m; 21 22 void Dfs(int u, int fa) 23 { 24 st[u] = cnt + 1; 25 int num = gift[u].size(); 26 for(int i = 0; i < num; i++) { 27 seq[++cnt] = gift[u][i]; 28 } 29 int len = edge[u].size(); 30 for(int i = 0; i < len; i++) { 31 int v = edge[u][i]; 32 if(v != fa) Dfs(v, u); 33 } 34 ed[u] = cnt; 35 } 36 37 int Lowbit(int x) 38 { 39 return x & (-x); 40 } 41 42 void Add(int x, int val) 43 { 44 for(int i = x; i <= cnt; i += Lowbit(i)) { 45 s[i] += val; 46 } 47 } 48 49 int Sum(int x) 50 { 51 int res = 0; 52 for(int i = x; i > 0; i -= Lowbit(i)) { 53 res += s[i]; 54 } 55 return res; 56 } 57 58 bool cmp(int a, int b) 59 { 60 return ed[a] < ed[b]; 61 } 62 63 void Init() 64 { 65 for(int i = 0; i <= n; i++) { 66 edge[i].clear(); 67 gift[i].clear(); 68 } 69 for(int i = 1; i <= n; i++) { 70 pos[i] = i; 71 } 72 memset(s, 0, sizeof(s)); 73 memset(mp, 0, sizeof(mp)); 74 } 75 76 int main() 77 { 78 int u, v; 79 80 while(scanf("%d %d", &n, &m) != EOF) { 81 Init(); 82 for(int i = 0; i < n-1; i++) { 83 scanf("%d %d", &u, &v); 84 edge[u].push_back(v); 85 edge[v].push_back(u); 86 } 87 while(m--) { 88 scanf("%d %d", &u, &v); 89 if(find(gift[u].begin(), gift[u].end(), v) == gift[u].end()) { 90 gift[u].push_back(v); 91 } 92 } 93 cnt = 0; 94 Dfs(1, -1); 95 sort(pos+1, pos+1+n, cmp); 96 for(int i = 1; i <= cnt; i++) { 97 if(mp[seq[i]] == 0) { 98 Add(i, 1); 99 mp[seq[i]] = i; //如果是第一次出现, mp[seq[i]]记录为当前位置 100 } 101 } 102 int right = 1; 103 for(int i = 1; i <= n; i++) { 104 int now = pos[i]; 105 while(right <= ed[now]) { 106 if(mp[seq[right]] != right) { //如果不是第一次出现 107 Add(mp[seq[right]], -1); //减去前一次出现的 108 Add(right, 1); 109 mp[seq[right]] = right; //重新定义这个数最近一次出现位置 110 } 111 right++; 112 } 113 ans[now] = Sum(ed[now]) - Sum(st[now] - 1); 114 } 115 printf("%d", ans[1]); 116 for(int i = 2; i <= n; i++) { 117 printf(" %d", ans[i]); 118 } 119 printf("\n"); 120 } 121 122 return 0; 123 }
另外第一次学习到 统计一个区间内有多少不同的数 这个问题的解法
基本思路是树状数组, 但是有重复的数, 要保证在一个区间内只更新过一次
所做的处理是用先遍历记录数字的数组a, 用数-位置数组 mp[a[i]] 来记录每个数第一次出现的位置并该点更新
这个操作对应的代码
1 for(int i = 1; i <= n; i++) { 2 if(mp[a[i]] == 0) { //如果是第一次出现, 记录第一次出现位置并更新 3 Add(i, 1); 4 mp[a[i]] = i; 5 } 6 }
记录左右查询, 每个查询按区间右端点R[i]从小到大排序
设一个扫描线k, 它的目的保证R[i]之前所有不同的点只更新过一次, 并且是在离R[i]最近的那个点更新
如此便可以写出这样一段代码
1 for(int i = 1; i <= query_num; i++) { 2 while(k <= R[i]) { 3 if(mp[a[i]] != k) { //如果不是最新次出现 4 Add(mp[a[k]], -1); //将上一次出现的更新-1 5 Add(k, 1); //将这个位置新出现的更新1 6 mp[a[k]] = k; //更新这个数最近一次的位置 7 } 8 k++; 9 } 10 ans[i] = Sum(ed[i]) - Sum(st[i] - 1); 11 }
恩, 就是这样...
HDU - 5156 Harry and Christmas tree
标签:
原文地址:http://www.cnblogs.com/Quinte/p/4894501.html