这几个题练习DFS序的一些应用。
问题引入:
给定一颗n(n <= 10^5)个节点的有根树,每个节点标有权值,现有如下两种操作:
1、C x y 以节点x的权值修改为y。
2、Q x 求出以节点x为根的子树权值和。
最直观的做法, 枚举一个子树内所有节点的权值加和。但这种做法的每一次讯问的时间复杂度是O(n)的,很明显无法满足题目的需要,我们需要更优的解法。
我们考虑DFS序的另外一种形式,当访问到一个节点时记下当前的时间戳,我们设它为L[x], 结束访问一个节点时当前的时间戳设为R[x]。则以x为根的子树刚好是下标为L[x]和R[x]中间的点。我们看下面这个例子。
有了DFS序这个性质我们就可以讲这个问题转化成一个区间求和问题。
每一次询问的复杂度就是O(logn)级别的,完全可以接受。
另外:很多人习惯在生成DFS序的时候,叶子节点的进出时间戳差1,我习惯于叶子节点的进出时间戳一样。这样根节点的时间戳为[1,n]
比如说:上图中如果采用叶子节点进出时间戳差1的写法,DFS序为:4、3、1、7、7、1、2、6、6、2、3、5、8、8、5、4.数量为2*n
如果按照我习惯的做法,DFS序为:4、3、1、7、2、6、5、8.
两种做法都可以,我习惯于第二种。
练习题:
第一题:http://acm.hdu.edu.cn/showproblem.php?pid=3887
题意:给定一棵树,求某一节点所在的子树中比它的值小的节点数量。
思路:求给定节点的时间戳区间,则在此区间的DFS序就是该节点的子树中的所有节点。统计这个区间中的比给定节点值小的数目即可。
这里我们可以变换一种思维,从后往前找。因为值最大的节点之间肯定都是比它小的。求出后删除掉,就不会影响小标号的节点统计了。
区间维护用线段树或者树状数组均可。我用线段树。
为了避免卡Vector,建树用前向星模拟一下。
为了避免卡递归DFS爆栈,用手工模拟非递归DFS。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
using namespace std; #define Maxn 100005
#define lx (x<<1)
#define rx ((x<<1) | 1)
#define MID ((l + r)>>1) int n,s;
int total = 0; //前向星
int first[Maxn];
int next[Maxn<<1];
struct Edge
{
int a,b;
}edge[Maxn<<1]; int S[Maxn<<2];
int vis[Maxn]; struct Node
{
int l,r;
}node[Maxn]; int nodeNum = 0;
int f[Maxn];
stack <int> st; void insert(int a,int b)
{
total++;
edge[total].a = a;
edge[total].b = b;
next[total] = first[a];
first[a] = total;
}
//递归dfs会爆栈
/*void dfs(int s)
{
nodeNum++;
vis[s] = 1;
node[s].l = nodeNum;
for(int i=first[s];i!=-1;i=next[i])
{
if(!vis[edge[i].b]) dfs(edge[i].b);
}
node[s].r = nodeNum;
}*/ void dfs(int s)
{
st.push(s);
while(!st.empty())
{
int flag = 0;
int t = st.top();
if(!vis[t])
{
nodeNum++;
node[t].l = nodeNum;
vis[t] = 1;
}
for(int i=first[t];i!=-1;i=next[i])
{
if(!vis[edge[i].b])
{
st.push(edge[i].b);
flag = 1;
}
}
//叶子节点
//把pop放在最后
if(!flag)
{
node[t].r = nodeNum;
st.pop();
}
}
}
//线段树相关
void pushUp(int x)
{
S[x] = S[lx] + S[rx];
}
void build(int l,int r,int x)
{
if(l == r)
{
S[x] = 1;
return;
}
build(l,MID,lx);
build(MID+1,r,rx);
pushUp(x);
}
void update(int p,int d,int l,int r,int x)
{
if(l == r)
{
S[x] += d;
return;
}
if(p<=MID) update(p,d,l,MID,lx);
else update(p,d,MID+1,r,rx);
pushUp(x);
}
int query(int L,int R,int l,int r,int x)
{
if(L<=l && r<=R)
{
return S[x];
}
int ans = 0;
if(L<=MID) ans += query(L,R,l,MID,lx);
if(MID+1<=R) ans += query(L,R,MID+1,r,rx);
return ans;
}
void init()
{
total = 0;
nodeNum = 0;
memset(first,-1,sizeof(first));
memset(next,-1,sizeof(next));
memset(vis,0,sizeof(vis));
memset(f,0,sizeof(f));
while(!st.empty())
{
st.pop();
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in2.txt","r",stdin);
#endif
int a,b;
while(scanf(" %d %d",&n,&s)!=EOF)
{
init();
if(n+s == 0) break;
for(int i=0;i<n-1;i++)
{
scanf(" %d %d",&a,&b);
insert(a,b);
insert(b,a);
}
dfs(s);
build(1,n,1);
for(int i=n;i>=1;i--)
{
f[i] = query(node[i].l,node[i].r,1,n,1) - 1;
update(node[i].l,-1,1,n,1);
}
for(int i=1;i<=n;i++)
{
i == n ? printf("%d\n",f[i]) : printf("%d ",f[i]);
}
}
return 0;
}
第二题: http://poj.org/problem?id=3321
题意是统计某一节点所在子树的节点个数。伴随着节点的删除增加操作。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <queue>
using namespace std; #define Maxn 100005
#define lx (x<<1)
#define rx ((x<<1) | 1)
#define MID ((l + r)>>1) //卡Vector,改成前向星吧
//点邻接的第一个边号
int first[Maxn];
int next[Maxn<<1];
int edgeNum;
struct Edge
{
int a,b;
}edge[Maxn<<1]; int vis[Maxn];
int total = 0;
int n,m;
int S[Maxn<<2]; struct Node
{
int l,r;
}node[Maxn]; void insert(int a,int b)
{
edgeNum++;
edge[edgeNum].a = a;
edge[edgeNum].b = b;
next[edgeNum] = first[a];
first[a] = edgeNum;
}
void dfs(int s)
{
//printf("hello world\n");
total++;
node[s].l = total;
vis[s] = 1;
for(int i = first[s];i!=-1;i = next[i])
{
if(!vis[edge[i].b]) dfs(edge[i].b);
}
node[s].r = total;
}
void pushUp(int x)
{
S[x] = S[lx] + S[rx];
}
void build(int l,int r,int x)
{
if(l == r)
{
S[x] = 1;
return;
}
build(l,MID,lx);
build(MID+1,r,rx);
pushUp(x);
}
int query(int L,int R,int l,int r,int x)
{
if(L<=l && r<=R) return S[x];
int ans = 0;
if(L<=MID) ans += query(L,R,l,MID,lx);
if(MID+1<=R) ans += query(L,R,MID+1,r,rx);
pushUp(x);
return ans;
}
void update(int p,int d,int l,int r,int x)
{
if(l == r)
{
S[x] = d - S[x];
return;
}
if(p<=MID) update(p,d,l,MID,lx);
else update(p,d,MID+1,r,rx);
pushUp(x);
} void init()
{
total = 0;
edgeNum = 0;
memset(vis,0,sizeof(vis));
memset(next,-1,sizeof(next));
memset(first,-1,sizeof(first));
} int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif int a,b,c;
char op[5];
init();
scanf(" %d",&n);
for(int i=0;i<n-1;i++)
{
scanf(" %d %d",&a,&b);
insert(a,b);
insert(b,a);
}
dfs(1);
build(1,n,1);
scanf(" %d",&m);
for(int i=0;i<m;i++)
{
scanf(" %s %d",op,&c);
if(op[0] == 'Q')
{
int ans = query(node[c].l,node[c].r,1,n,1);
printf("%d\n", ans);
}
else if(op[0] == 'C')
{
update(node[c].l,1,1,n,1);
}
}
return 0;
}
第三题:http://www.lydsy.com/JudgeOnline/problem.php?id=1103
某一节点到根节点距离的维护问题。
思路:使用DFS O(n)预处理出所有节点到根的路径上的权值和,将一个点的权值增加b就相当于将这个以这个点为根的子树中所有节点到根的距离增加b,使用线段树的lazy标记。
虽然此题是区间更新,单点查询。我们也可以用区间更新,区间查询的思路。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
using namespace std; #define Maxn 250005
#define lx (x<<1)
#define rx ((x<<1) | 1)
#define MID ((l + r)>>1) int n,m; int first[Maxn];
int next[Maxn];
int father[Maxn];
int vis[Maxn];
int rank[Maxn];
//本题处理成有向边,就不用开双倍内存了
struct Edge
{
int a,b;
}edge[Maxn];
int edgeNum = 0;
int totalNum = 0; struct Node
{
int l,r;
int roadNum;
}node[Maxn]; //处理成有向边吧
void insert(int a,int b)
{
edgeNum++;
edge[edgeNum].a = a;
edge[edgeNum].b = b;
next[edgeNum] = first[a];
first[a] = edgeNum;
}
void dfs(int s,int deep)
{
totalNum++;
node[s].l = totalNum;
rank[totalNum] = s;
node[s].roadNum = deep;
vis[s] = 1;
for(int i=first[s];i!=-1;i=next[i])
{
if(!vis[edge[i].b]) dfs(edge[i].b,deep+1);
}
node[s].r = totalNum;
}
void init()
{
memset(first,-1,sizeof(first));
memset(next,-1,sizeof(next));
memset(father,0,sizeof(father));
memset(vis,0,sizeof(vis));
edgeNum = 0;
totalNum = 0;
} int S[Maxn<<2];
int D[Maxn<<2]; void pushUp(int x)
{
//S[x] = S[lx] + S[rx];
//我们只需要叶节点的信息,写个伪的吧
return;
}
void pushDown(int l,int r,int x)
{
if(D[x])
{
D[lx] += D[x];
D[rx] += D[x];
S[lx] += (MID-l+1)*D[x];
S[rx] += (r-MID)*D[x];
D[x] = 0;
}
}
void build(int l,int r,int x)
{
if(l == r)
{
S[x] = node[rank[l]].roadNum;
return;
}
build(l,MID,lx);
build(MID+1,r,rx);
pushUp(x);
}
void update(int L,int R,int d,int l,int r,int x)
{
if(L<=l && r<=R)
{
D[x] += d;
S[x] += (r - l + 1)*d;
return;
}
pushDown(l,r,x);
if(L<=MID) update(L,R,d,l,MID,lx);
if(MID+1<=R) update(L,R,d,MID+1,r,rx);
pushUp(x);
}
int query(int L,int R,int l,int r,int x)
{ if(L<=l && r<=R)
{
return S[x];
}
int ans = 0;
pushDown(l,r,x);
if(L<=MID) ans += query(L,R,l,MID,lx);
if(R>=MID+1) ans += query(L,R,MID+1,r,rx);
return ans;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
init();
int a,b;
int opa,opb;
char op[5];
scanf(" %d",&n);
for(int i=0;i<n-1;i++)
{
scanf(" %d %d",&a,&b);
if(a>b) swap(a,b);
insert(a,b);
}
dfs(1,0); build(1,n,1); scanf(" %d",&m);
m = n + m - 1;
for(int i=0;i<m;i++)
{
scanf(" %s",op);
if(op[0] == 'W')
{
scanf(" %d",&opa);
int ans = query(node[opa].l,node[opa].l,1,n,1);
printf("%d\n", ans);
}
else if(op[0] == 'A')
{
scanf(" %d %d",&opa,&opb);
if(opa>opb) swap(opa,opb);
update(node[opb].l,node[opb].r,-1,1,n,1);
}
}
return 0;
}
Hdu 3887 Counting Offspring \ Poj 3321 Apple Tree \BZOJ 1103 [POI2007]大都市meg的更多相关文章
-
POJ.3321 Apple Tree ( DFS序 线段树 单点更新 区间求和)
POJ.3321 Apple Tree ( DFS序 线段树 单点更新 区间求和) 题意分析 卡卡屋前有一株苹果树,每年秋天,树上长了许多苹果.卡卡很喜欢苹果.树上有N个节点,卡卡给他们编号1到N,根 ...
-
POJ - 3321 Apple Tree (线段树 + 建树 + 思维转换)
id=10486" target="_blank" style="color:blue; text-decoration:none">POJ - ...
-
POJ 3321 Apple Tree 【树状数组+建树】
题目链接:http://poj.org/problem?id=3321 Apple Tree Time Limit: 2000MS Memory Limit: 65536K Total Submiss ...
-
POJ 3321 Apple Tree(DFS序+线段树单点修改区间查询)
Apple Tree Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 25904 Accepted: 7682 Descr ...
-
poj 3321:Apple Tree(树状数组,提高题)
Apple Tree Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 18623 Accepted: 5629 Descr ...
-
poj 3321 Apple Tree dfs序+线段树
Apple Tree Time Limit: 2000MS Memory Limit: 65536K Description There is an apple tree outsid ...
-
hdu 3887 Counting Offspring dfs序+树状数组
Counting Offspring Time Limit: 15000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Othe ...
-
(简单) POJ 3321 Apple Tree,树链剖分+树状数组。
Description There is an apple tree outside of kaka's house. Every autumn, a lot of apples will grow ...
-
HDU 3887 Counting Offspring(DFS序+树状数组)
Counting Offspring Time Limit: 15000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Othe ...
随机推荐
-
Google云平台对于2014世界杯半决赛的预测,德国阿根廷胜!
由于本人是个足球迷,前段日子Google利用自己云平台预测世界杯八进四的比赛并取得了75%的正确率的事情让我振动不小.虽然这些年一直听说大数据的预测和看趋势能力如何如何强大,但这次的感受更加震撼,因为 ...
-
Spark难道比oracle性能还差?百万级数据测试性能
本人对大数据方面也是刚刚研究,由于工作需要在实时查询与统计的性能方面要深入学习.现测试性能如下: 环境:VirtualBox host-only ubuntu版本: Linux master 4 ...
-
document.documentElement.clientWidth
document.documentElement.clientWidth 摘自:http://blog.sina.com.cn/s/blog_6f1f9ead0100n1f6.html 关于获取各种浏 ...
-
copy和assign的使用和区别
1.使用copy和assign都可以进行修饰属性或者变量. 2.区别: (1)copy的使用:使用这个进行修饰的属性,当已经进行初始化之后,就无法再改变属性的数据. 如: @property (cop ...
-
<;关于数据仓库>;基于docker的Mysql与Hadoop/Hive之间的数据转移 (使用Apache Sqoop™)
原创博客,转载请联系博主! 摘要:本文介绍了如何使用docker快速搭建一个可以从外部访问的mysql服务容器,和由docker搭建的分布式Hadoop文件系统,并且使用ApacheSqoop完成将m ...
-
bnuoj 29375 Two Strings(字符串?)
http://www.bnuoj.com/bnuoj/problem_show.php?pid=29375 [题意]:可以对两字符串进行如下操作: 1.可以无损耗交换相邻两个字符(可以理解成交换任意字 ...
-
basename usage in linux
作用:去掉文件的目录和后缀 1.去掉文件路径 jenkins@work:~/ci/script$ basename /backup/jenkins/ci/script/Release.sh.bak R ...
-
五、MP3文件认识上的几个误区
1.每帧播放时长都为26ms? 很多博客和文章都提到,Mp3文件每个帧的播放时长(Frame_PlayingTime)是26ms,这个结论是错误的.公式应该是这样的: 一个帧的播放时长=一个帧的采样个 ...
-
rsync+inotify
一.rsync 1.1rsync是啥 相当于cp.scp.rm等工具,但优于这些工具,主要用在数据备份 1.2.rsync安装 yum -y install rsync --update 客户端删除文 ...
-
Jetty学习四:部署到Jetty
转自:http://www.tuicool.com/articles/NrENjq Web应用的框架 标准Jetty发布版本能部署标准servlet Spec Web应用和Jetty内部Context ...