POJ 3321:Apple Tree + HDU 3887:Counting Offspring(DFS序+树状数组)
http://poj.org/problem?id=3321
http://acm.hdu.edu.cn/showproblem.php?pid=3887
POJ 3321:
题意:给出一棵根节点为1的边不一定的树,然后给出问题:询问区间和 或者 节点值更新。
HDU 3887:
题意:和POJ 3321的题意差不多,只不过对每个节点询问不包含该节点的区间和
思路:今天才学了下才知道有DFS序这种东西,加上树状数组处理一下区间和 和 节点更新。
DFS序大概就是我们在DFS遍历一棵树的时候,在进入这个节点的时候,我们用一个变量记录此时的时间(代码中用cnt表示,也是可以看做是目前已经遍历过的节点的数目),然后分别用一个数组表示开始搜索这个节点的时间,和离开这个节点的时间(代码中用st代表开始,用ed代表结束)。然后我们可以发现在这段时间里面遍历过的节点都是该节点的儿子,即例如HDU3887这组样例:
15 7
7 10
7 1
7 9
7 3
7 4
10 14
14 2
14 13
9 11
9 6
6 5
6 8
3 15
3 12
0 0
我们的遍历顺序是这样的:7 1 1 3 15 15 12 12 3 4 4 10 14 2 2 13 13 14 10 9 11 11 6 5 5 8 8 6 9 7(手打的。。)
分别对应的下标是这样的:例如根节点7,它管辖的范围从前到后全部是,说明它就是根节点了。那么它的范围是【1,30】。
1 int cnt = 0; 2 void dfs(int u, int fa) 3 { 4 que[++cnt] = u; 5 for(int k = head[u]; ~k; k = edge[k].nxt){ 6 int v = edge[k].v; 7 if( v == fa ) continue; 8 dfs(v, u); 9 } 10 que[++cnt] = u; 11 } 12 dfs(7, -1);
我这里用的是前向星存的图,然后从根节点7开始遍历。
我先用一个数组来装遍历过的点的顺序,即que数组为我上面列出来的那个序列。
然后我们就按下面这样处理
1 for(int i = 1; i <= cnt; i++) { 2 if( st[que[i]] == 0 ) st[que[i]] = i; 3 else se[que[i]] = i; 4 }
把开始的位置和结束的位置分别装在st数组和ed数组中。
然后我们就可以根据这些条件建立起树状数组了。
询问的区间和为Query( ed[node] ) - Query( st[node] - 1 ) / 2(因为我们重复计数了,每个节点枚举了两次) ,更新就是Update( ed[node], value ); Update( st[node], value );
下面给出HDU 3887 的代码。
1 #include <cstdio> 2 #include <algorithm> 3 #include <cstring> 4 #include <iostream> 5 #include <vector> 6 using namespace std; 7 #define N 100010 8 9 int st[N], se[N], ans[N], cnt; 10 int bit[2*N],que[2*N]; 11 int head[N], tot; 12 struct node 13 { 14 int v, nxt; 15 }edge[N*2]; 16 //vector <int> edge[N]; 17 //dfs序 + 树状数组 18 void addedge(int u, int v) 19 { 20 edge[tot].v = v; 21 edge[tot].nxt = head[u]; 22 head[u] = tot++; 23 } 24 25 void dfs(int u, int fa) 26 { 27 que[++cnt] = u; 28 for(int k = head[u]; ~k; k = edge[k].nxt){ 29 int v = edge[k].v; 30 if( v == fa ) continue; 31 dfs(v, u); 32 } 33 que[++cnt] = u; 34 } 35 36 int lowbit(int x) 37 { 38 return x & (-x); 39 } 40 41 void Update(int x, int value) 42 { 43 while( x <= cnt ) { 44 bit[x] += value; 45 x += lowbit(x); 46 } 47 } 48 49 int Query(int x) 50 { 51 int ans = 0; 52 while( x > 0 ) { 53 ans += bit[x]; 54 x -= lowbit(x); 55 } 56 return ans; 57 } 58 59 int main() 60 { 61 int n,p; 62 while(scanf("%d%d", &n, &p), n + p) { 63 64 memset(head, -1, sizeof(head)); 65 tot = 0; 66 67 memset(bit,0,sizeof(bit)); 68 memset(st,0,sizeof(st)); 69 // for(int i = 1; i <= n; i++) 70 // edge[i].clear(); 71 for(int i = 1; i < n; i++) { 72 int u, v; 73 scanf("%d%d", &u, &v); 74 addedge(u, v); 75 addedge(v, u); 76 // edge[u].push_back(v); 77 // edge[v].push_back(u); 78 } 79 cnt = 0; 80 dfs(p, p); 81 82 for(int i = 1; i <= cnt; i++) { 83 if( st[que[i]] == 0 ) st[que[i]] = i; 84 else se[que[i]] = i; 85 } 86 87 for(int i = 1; i <= cnt; i++) { 88 // printf("%d ", que[i]); 89 Update(i, 1); 90 } 91 putchar('\n'); 92 //因为题意中要求子树中的节点不能比根节点的数大,因此从后往前枚举, 93 //然后删除掉枚举过的节点,这样就能保证后面的枚举中子树的点的数不会比根节点的大了 94 //而且题目的询问是不包含该询问的节点的,因此是(Query(se[i] - 1) - Query(st[i])) / 2 95 //而不是( Query(se[i]) - (Query[st[i]] - 1) ) / 2 96 for(int i = n; i > 0; i--) { 97 ans[i] = (Query(se[i] - 1) - Query(st[i])) / 2; 98 Update(se[i], -1); 99 Update(st[i], -1); 100 } 101 102 for(int i = 1; i <= n; i++) { 103 printf("%d", ans[i]); 104 if( i == n ) printf("\n"); 105 else printf(" "); 106 } 107 } 108 return 0; 109 }
我根据上题的代码也写了一份POJ 3321的代码,顺利AC了之后,看了看别人的代码,发现自己处理的有点复杂 (就是MDZZ)。
先上一份看了别人之后修改过的DFS的代码
1 void dfs(int u, int fa) 2 { 3 st[u] = ++cnt; 4 for(int k = head[u]; ~k; k = edge[k].nxt){ 5 int v = edge[k].v; 6 if( v != fa ) dfs(v, u); 7 } 8 ed[u] = cnt; 9 }
其实完全可以抛弃上一题的que数组,直接用这样的方式来处理st和ed数组。
虽然我觉得用que数组的话真的很好理解,但是变得比较冗杂了。
我们还是举HDU 3887的例子(谁让POJ 3321这个样例太不好举例了。。)
我们这样处理的话就是直接去掉我上面的重复的数字,变成:
node:7 1 3 15 12 4 10 14 2 13 9 11 6 5 8
st和ed两个数组对应的下标分别是:
st: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
ed:15 2 5 4 5 6 10 10 9 10 15 12 15 14 15 (对不齐,将就看看吧= =)
我们直接根据这样就可以建立一个树状数组了。
例如节点7的领域:【1,15】,
节点1的领域:【2,2】,
以此类推。
然后因为我们的树状数组变了,所以我们的询问和更新也肯定变了。
询问还是 【 st[node] - 1 ,ed[node] 】,只不过区间的值变小了,因为没有重复,我们不用对答案除以2了。
更新的区间就只要更新st[node]了,因为看上面,st[node]代表的是当下标取node时对应于该节点的左端点,所以只要更新st[node]就可以了。
还有这题可能卡了vector,我用的是前向星,一开始用vector超时了。
下面给出代码
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 using namespace std; 6 #define N 100005 7 struct node 8 { 9 int v, nxt; 10 }edge[N*2]; 11 int bit[N], st[N], ed[N], head[N], mark[N], cnt, tot, n; 12 13 void add(int u,int v) 14 { 15 edge[tot].v = v; 16 edge[tot].nxt = head[u]; 17 head[u] = tot++; 18 edge[tot].v = u; 19 edge[tot].nxt = head[v]; 20 head[v] = tot++; 21 } 22 23 int lowbit(int x) 24 { 25 return x & (-x); 26 } 27 28 int Query(int x) 29 { 30 int ans = 0; 31 while( x > 0 ){ 32 ans += bit[x]; 33 x -= lowbit(x); 34 } 35 return ans; 36 } 37 38 void Update(int x, int value) 39 { 40 while( x <= n ){ 41 bit[x] += value; 42 x += lowbit(x); 43 } 44 } 45 46 void dfs(int u, int fa) 47 { 48 st[u] = ++cnt; 49 for(int k = head[u]; ~k; k = edge[k].nxt){ 50 int v = edge[k].v; 51 if( v != fa ) dfs(v, u); 52 } 53 ed[u] = cnt; 54 } 55 56 int main() 57 { 58 tot = 0, cnt = 0; 59 memset(bit, 0, sizeof(bit)); 60 memset(head, -1, sizeof(head)); 61 scanf("%d", &n); 62 for(int i = 1; i < n; i++){ 63 int u, v; 64 scanf("%d%d", &u, &v); 65 add(u, v); 66 } 67 for(int i = 1; i <= n; i++){ 68 Update(i, 1); 69 mark[i] = 1; 70 } 71 72 dfs(1, -1); 73 74 int q; 75 scanf("%d", &q); 76 while(q--){ 77 char s[3]; 78 int node; 79 scanf("%s%d", s, &node); 80 if(s[0] == 'Q'){ 81 int ans = Query(ed[node]) - Query(st[node] - 1); 82 printf("%d\n", ans); 83 } 84 else{ 85 mark[node] *= -1; 86 Update(st[node], mark[node]); 87 } 88 printf("%d %d\n", bit[st[1]], bit[ed[1]]); 89 } 90 return 0; 91 } 92 //15 93 //1 10 94 //1 7 95 //1 9 96 //1 3 97 //1 4 98 //10 14 99 //14 2 100 //14 13 101 //9 11 102 //9 6 103 //6 5 104 //6 8 105 //3 15 106 //3 12 107 //8 108 //Q 1 109 //C 1 110 //C 3 111 //C 5 112 //Q 1 113 //C 1 114 //Q 1 115 //Q 5