Apple Tree(树状数组+dfs序+邻接表数组(链式前向星) )

本文介绍了如何利用树状数组和DFS序解决一个关于苹果树的编程问题。该问题涉及树的分支连接,苹果生长与采摘,以及查询子树内苹果总数的请求。通过将问题转化为点修改和区间查询,可以有效利用树状数组进行求解。

链接:http://poj.org/problem?id=3321

Description

There is an apple tree outside of kaka's house. Every autumn, a lot of apples will grow in the tree. Kaka likes apple very much, so he has been carefully nurturing the big apple tree.

The tree has N forks which are connected by branches. Kaka numbers the forks by 1 to N and the root is always numbered by 1. Apples will grow on the forks and two apple won't grow on the same fork. kaka wants to know how many apples are there in a sub-tree, for his study of the produce ability of the apple tree.

The trouble is that a new apple may grow on an empty fork some time and kaka may pick an apple from the tree for his dessert. Can you help kaka?

 

 

Input

The first line contains an integer N (N ≤ 100,000) , which is the number of the forks in the tree.
The following N - 1 lines each contain two integers u and v, which means fork u and fork v are connected by a branch.
The next line contains an integer M (M ≤ 100,000).
The following M lines each contain a message which is either
"C x" which means the existence of the apple on fork x has been changed. i.e. if there is an apple on the fork, then Kaka pick it; otherwise a new apple has grown on the empty fork.
or
"Q x" which means an inquiry for the number of apples in the sub-tree above the fork x, including the apple (if exists) on the fork x
Note the tree is full of apples at the beginning

Output

For every inquiry, output the correspond answer per line.

Sample Input

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

Sample Output

3
2

 

题目大意:
一棵树上长了苹果,每一个树枝节点上有长苹果和不长苹果两种状态,有两种操作,一种操作能够改变树枝上苹果的状态,另一种操作询问某一树枝节点及以下的所有的苹果有多少。

 

思路:在dfs序中,子树处于一个连续区间中。所以这题可以转化为:点修改,区间查询。可转化树状数组来处理。

 

代码:

#include<iostream>
using namespace std;
const int maxn=210100;
struct node
{
    int to;
    int next;
}e[maxn];
char s;int x;
int in[maxn],out[maxn],c[maxn],cnt;
int n,m,tot,head[maxn],vis[maxn];
void add_edge(int u,int v)
{
    tot++;
    e[tot].to=v;
    e[tot].next=head[u];
    head[u]=tot;
}
int lowbit(int x)
{
    return x&(-x);
}
void dfs(int x)
{
    in[x]=++cnt;
    for(int i=head[x];i;i=e[i].next)
    dfs(e[i].to);
    out[x]=cnt;
}
void update(int x,int add)
{
    while(x<=n)
    {
        c[x]+=add;
        x+=lowbit(x);
    }
}
int sum(int x)
{
    int s=0;
    while(x>0)
    {
        s+=c[x];
        x-=lowbit(x);
    }
    return s;
}
int main()
{
    int a,b;
    cin>>n;
    for(int i=1;i<n;i++)
    {
        cin>>a>>b;
        add_edge(a,b);
    }
    dfs(1);
    for(int i=1;i<=n;i++)
    update(in[i],1),vis[i]=1;
    cin>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>s>>x;
        if(s=='C')
        {
            if(vis[x])update(in[x],-1);
            else update(in[x],1);
            vis[x]=1-vis[x];
        }
        if(s=='Q')
        cout<<sum(out[x])-sum(in[x]-1)<<endl;
    }
    return 0;
}

 

<think>好的,用户想了解链式邻接表链式前向星的区别,包括数据结构和适用场景。我需要先回忆这两种结构的基本概念,然后比较它们的实现方式和应用场景。 首先,链式邻接表,我记得这是图的一种存储方式,每个顶点对应一个链表,存储其邻接点。比如引用[1]提到链式邻接表是基础的高级存储结构之一。而链式前向星,引用[4]说它本质是链表,但多了head数组记录头结点,可能更高效? 接下来看具体实现。链式邻接表每个顶点维护一个链表,每个节点包含邻接顶点和权重等信息,可能还有下个节点的指针。而链式前向星,根据引用[4]的代码,结构体EDGE包含v、w、next,通过head数组和边的添加方式来连接。看起来前向星将所有边存在一个数组中,通过索引来链接,而邻接表每个顶点独立链表。 空间方面,邻接表每个边都需要存储指针,而前向星可能更紧凑,用数组代替指针,减少内存碎片。引用[4]提到前向星的空间效率是O(m),适合稠密图?或者是不是适合稀疏图?可能记错了,需要再确认。 适用场景的话,邻接表实现直观,适合动态增删边,但指针可能占用额外空间。前向星用数组模拟链表,访问连续内存,缓存友好,适合处理大规模数据,尤其是静态图。引用[3]和[4]提到邻接矩阵、邻接表、前向星三种方式,前向星在空间和时间上可能更优。 可能用户混淆了两种结构,需要明确它们的存储方式、实现细节和性能差异。比如,邻接表的每个顶点链表是独立的,而前向星通过全局数组和head指针来管理,遍历时可能更高效。另外,前向星的边添加是头插法,所以遍历顺是逆的,但这对某些算法不影响。 总结区别点:数据结构实现、存储方式、内存使用、适用场景(动态vs静态,稀疏vs稠密)。需要指出各自的优缺点,帮助用户理解何时选择哪种结构。</think>### 链式邻接表 vs 链式前向星:数据结构区别及适用场景 #### 一、数据结构区别 1. **链式邻接表** - **核心结构**:每个顶点维护一个独立的链表,链表中节点存储邻接顶点的信息(如顶点编号、边权值等)[^1][^3]。 - **实现方式**:通过动态内存分配或结构体指针实现链表,例如: ```c struct Node { int v; // 邻接顶点 int w; // 边权值 Node* next; // 指向下一个邻接点 }; Node* head[N]; // 每个顶点的链表头指针 ``` 2. **链式前向星** - **核心结构**:通过数组模拟链表,使用静态存储结构。所有边存储在一个数组中,每条边记录邻接顶点、权值以及“下一条边的索引”[^4]。 - **实现方式**: ```c struct EDGE { int v, w, next; // next指向同起点的上一条边的索引 } e[M]; // 边数组 int head[N]; // head[u]记录顶点u的最后添加边的索引 int tot = 1; // 当前边的总数 ``` #### 二、关键差异对比 | **特性** | **链式邻接表** | **链式前向星** | |----------------|----------------------------------|----------------------------------| | **存储方式** | 动态链表(指针) | 静态数组模拟链表 | | **内存占用** | 较高(需存储指针) | 较低(仅需数组索引)[^4] | | **访问效率** | 可能因内存碎片降低缓存命中率 | 数组连续存储,缓存友好 | | **动态增删边** | 支持(直接操作链表) | 不支持(需预先分配数组) | | **遍历顺** | 正(按添加顺) | 逆(头插法导致)[^4] | #### 三、适用场景 1. **链式邻接表** - **推荐场景**:需要频繁动态增删边的图(如社交网络动态更新)[^3]。 - **缺点**:内存碎片化问题,对大规模稠密图不友好。 2. **链式前向星** - **推荐场景**:静态图或边数已知的稠密图(如竞赛题目、固定路网)[^4]。 - **优点**:内存紧凑、访问高效,适合需要高性能遍历的场景。 #### 四、代码示例 1. **链式邻接表添加边** ```c void add_edge(int u, int v, int w) { Node* new_node = (Node*)malloc(sizeof(Node)); new_node->v = v; new_node->w = w; new_node->next = head[u]; head[u] = new_node; } ``` 2. **链式前向星添加边** ```c void add(int u, int v, int w) { e[tot].v = v; e[tot].w = w; e[tot].next = head[u]; // 头插法 head[u] = tot++; } ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值