您的位置:首页 > 移动开发

POJ-3321-Apple Tree,线段树,树状数组。

2017-07-16 20:11 423 查看
具体题目是,POJ-3321,这里不给了;

数据先给了n;代表了一共有n个编号(对于n个结点);

大致题意是给一颗根节点为编号1的树,以及接下来的n-1条边(x,y) 根据discuss区描述这条边就是x to y,不用考虑y to x;(这不是重点)

再是m个操作(点修改and这个点的子树的结点个数查询);

3
1 2
1 3
3
Q 1
C 2
Q 1

样例:根节点1的两个子树是2,3;所以Q (1) 返回3(1,2,3) 当C(2)后,就只有两个了 返回2

首先不管怎么样,我们先得根据边建树(图);

用邻接表

vector<int> tree
;

scanf("%d%d",&x,&y);

tree[x].push_back(y);

这样子就行了

我刚拿这个题目并不会使用线段树和树状数组,所以我直接暴力模拟感觉也能过

思路是并查集思想,用cnt【i】来存放编号i结点的子树的结点个数

怎么求cnt[i]呢?当然是DFS(后根遍历)一遍cnt[i]=sum of (cnt[子树]);然后顺便把 fa【i】连接上就可以找到祖先了;

修改一个点后,向上更新祖先的值,这样要是树比较平衡的话更新的复杂度是O log(n)

查询是O(1);

但是TLE;很显然这棵树比较极端;

所以我百度了题解发现都是用树状数组或是线段树来写的。

首先你们得知道这两个数据结构,这里给两个我看懂的博客;

线段树             

树状数组 

当然虽说这是两个基础数据结构,但是对菜鸡(我)也不好懂。

然后我看完这两种数据结构感觉有点想法了,然后回过头来看题目,完全不知道怎么用 有没有?这树的编号和区间有半毛钱关系啊!!!;

所以我有仔细的研究了一番别人的题解;

震惊!!!他们竟然给树的结点重新编号

然而我不理解呀!!为什么可以可以这样编号,把树的结构给线性化了,而且刚好可以套上树状数组啊!

好吧!先不吐槽了!先看一下他们是怎么编号的;重点!!注意 L数组,R数组!!;其他的Tree是上面的树,C数组是树状数组要用的空间

这是我模仿写的代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#include<cmath>
#include<vector>
#define lbt(x) x&-x
#define mem(a) memset(a, 0, sizeof(a))
using namespace std;
const int N=1e5+100;
vector<int> tree
; //
int L
,R
; //L是该节点在新编号下的左边界也是他本身的新编号,比如结点1 L【1】=1,R【1】=n;
int C
; //
bool apple
; //
int n,m,key;
void add(int x,int val){ 修改点
while(x<=n){
C[x]+=val;
x+=lbt(x);
}
}

int read(int x){ 求1-x的和;
int sum=0;
while(x>0){
sum+=C[x];
x-=lbt(x);
}
return sum;
}

void dfs(int i){ 这才是关键
L[i]=key;
for(int j=0;j<tree[i].size();++j){
key++;
dfs(tree[i][j]);
}
R[i]=key;
}
void init(){
mem(L);mem(R);mem(C);
for(int i=0;i<N;i++) tree[i].clear();
}

int main(){
//freopen("in.txt","r",stdin);
cin>>n;
init();
for(int i=1;i<n;i++){
int x,y;scanf("%d%d",&x,&y);
tree[x].push_back(y);
}
for(int i=1;i<=n;i++){
apple[i]=true;
add(i,1);
}
key=1;dfs(1);cin>>m;
for(int i=1;i<=m;i++){
char op[2];int x;scanf("%s%d",op,&x);
if(op[0]=='Q') printf("%d\n",read(R[x])-read(L[x]-1));
else{
if(apple[x]==true){apple[x]=false;add(L[x],-1);}
else{apple[x]=true;add(L[x],1);}
}
}

大家仔细揣摩一番,不知道看懂了没有,反正刚开始我是一脸MB;

其中 L是该节点在新编号下的左边界也是他本身的新编号,比如结点1   L【1】=1,R【1】=n,也就是根节点是新编号的数组的第一个结点。控制着(1,n)的和;

正好跟1 的子树就是整棵树一样;

那这是为什么呢?;

这是因为这里在重新编号的时候充分考虑到DFS的特点,

把!树形!对应关系转换成一个!线性!的先序遍历序列



这样做的原因有两点

1:这样遍历能把一颗颗子树在用线性的关系隔开,如图三颗子树隔开了;

2:刚好符合树状数组的求解;一颗树从根到最后一个叶子结点,就是区间最右端-区间最左端

到此这个问题告诉我重新地理解了DFS--线性化;并加强了对两种数据结构的理解;

最后给上用线段树模板ac的代码;

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#include<cmath>
#include<vector>
#define mem(a) memset(a, 0, sizeof(a))
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
using namespace std;
const int N=1e5+100;
vector< vector<int> > tree(N);
int L
,R
,A
;bool apple
;
int Sum[N<<2];
int n,m,key;
void init(){
mem(L);mem(R);
for(int i=1;i<=n;i++){
apple[i]=true;
A[i]=1;
}
}
void PushUp(int rt){Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];}
//Build函数建树
void Build(int l,int r,int rt){ //l,r表示当前节点区间,rt表示当前节点编号
if(l==r) {//若到达叶节点
Sum[rt]=A[l];//储存数组值
return;
}
int m=(l+r)>>1;
//左右递归
Build(l,m,rt<<1);
Build(m+1,r,rt<<1|1);
//更新信息
PushUp(rt);
}
void Update(int L,int C,int l,int r,int rt){//l,r表示当前节点区间,rt表示当前节点编号
if(l==r){//到叶节点,修改
Sum[rt]+=C;
return;
}
int m=(l+r)>>1;
//根据条件判断往左子树调用还是往右
if(L <= m) Update(L,C,l,m,rt<<1);
else       Update(L,C,m+1,r,rt<<1|1);
PushUp(rt);//子节点更新了,所以本节点也需要更新信息
}
int Query(int L,int R,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
if(L <= l && r <= R){
//在区间内,直接返回
return Sum[rt];
}
int m=(l+r)>>1;
//累计答案
int ANS=0;
if(L <= m) ANS+=Query(L,R,l,m,rt<<1);
if(R >  m) ANS+=Query(L,R,m+1,r,rt<<1|1);
return ANS;
}
void dfs(int i){
L[i]=key;
for(int j=0;j<tree[i].size();++j){
key++;
dfs(tree[i][j]);
}
R[i]=key;
}
int main(){
//freopen("in.txt","r",stdin);
cin>>n;
init();
for(int i=1;i<n;i++){
int x,y;scanf("%d%d",&x,&y);
tree[x].push_back(y);
}
key=1;dfs(1);Build(1,n,1);cin>>m;
for(int i=1;i<=m;i++){
char op[2];int x;scanf("%s%d",op,&x);
if(op[0]=='Q') printf("%d\n",Query(L[x],R[x],1,n,1));
else{
if(apple[x]==true){apple[x]=false;Update(L[x],-1,1,n,1);}
else{apple[x]=true;Update(L[x],1,1,n,1);}
}
}
}


vector< vector<int> > tree(N);

注意这里我改成了这样建树,从1.8s到了1s 具体涉及到vector的使用效率问题
看这个文章就明白了

4000
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息