poj 3417 Network(统计边被环覆盖的次数)

探讨了在一棵树中通过删除一条原始边和一条新增边将其至少分为两部分的问题,使用树状结构和DP算法来求解边的覆盖次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Network
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 3460 Accepted: 984

Description

Yixght is a manager of the company called SzqNetwork(SN). Now she's very worried because she has just received a bad news which denotes that DxtNetwork(DN), the SN's business rival, intents to attack the network of SN. More unfortunately, the original network of SN is so weak that we can just treat it as a tree. Formally, there are N nodes in SN's network, N-1 bidirectional channels to connect the nodes, and there always exists a route from any node to another. In order to protect the network from the attack, Yixght builds M new bidirectional channels between some of the nodes.

As the DN's best hacker, you can exactly destory two channels, one in the original network and the other among the M new channels. Now your higher-up wants to know how many ways you can divide the network of SN into at least two parts.

Input

The first line of the input file contains two integers: N (1 ≤ N ≤ 100 000), M (1 ≤ M ≤ 100 000) — the number of the nodes and the number of the new channels.

Following N-1 lines represent the channels in the original network of SN, each pair (a,b) denote that there is a channel between node a and node b.

Following M lines represent the new channels in the network, each pair (a,b) denote that a new channel between node a and node b is added to the network of SN.

Output

Output a single integer — the number of ways to divide the network into at least two parts.

Sample Input

4 1
1 2
2 3
1 4
3 4

Sample Output

3
 
一下引自http://www.cppblog.com/Yuan/archive/2010/07/11/120101.html
题意:给出一棵树,再给出M条新边,问删除一条树边以及一条新边,使之至少变为两部分的方案数

    对于新边(a,b),会在a->LCA(a,b)->b这里形成一个环,所以删除新边(a,b)以及这个环上的没有被其他环覆盖的边
    即可分成两部分。所以问题转化为求每条边被环覆盖的次数
    设dp[x]表示x所在的父边被覆盖的次数
    引进一条新边(a,b)后,dp[a]++,dp[b]++
    而这个环上的其他边的统计可以用treeDP解决,即for(v)
                                                      dp[u]+=dp[v]
    注意到LCA(a,b)的父边是不在环上的,所以每次引进新边(a,b),dp[LCA[a,b]]-=2
    最后,if(dp[i]==1)ans++        删除该边及覆盖它的那个环
           if(dp[i]==0)ans+=M  表明这条树边是桥,删除它及任意一条新边都可以
 
 
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
#include <map>
#include <cstdlib>
#define L(rt) (rt<<1)
#define R(rt) (rt<<1|1)
#define ll long long
using namespace std;

const int maxn=100005;
struct node
{
    int v,next;
}edge[maxn*2];
struct node1
{
    int v,next,id;
}que[maxn*2];
int G[maxn],Q[maxn],fa[maxn];
int dp[maxn],lca[maxn];
bool vis[maxn];
int n,m,num,numq;
void init()
{
    memset(G,-1,sizeof(G));
    memset(Q,-1,sizeof(Q));
    memset(dp,0,sizeof(dp));
    num=numq=0;
}
void add(int u,int v)
{
    edge[num].v=v;
    edge[num].next=G[u];
    G[u]=num++;
}
void addq(int u,int v,int id)
{
    que[numq].v=v;
    que[numq].id=id;
    que[numq].next=Q[u];
    Q[u]=numq++;
}
int Find_set(int x)
{
    return x==fa[x]?x:fa[x]=Find_set(fa[x]);
}
void Union(int a,int b)
{
    int ra=Find_set(a),rb=Find_set(b);
    if(ra!=rb) fa[rb]=ra;
}
void input()
{
    int a,b;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        addq(a,b,i);
        addq(b,a,i);
        dp[a]++;
        dp[b]++;
    }
}
void dfs(int u)
{
    fa[u]=u;
    vis[u]=true;
    for(int i=G[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(!vis[v])
        {
            dfs(v);
            Union(u,v);
        }
    }
    for(int i=Q[u];i!=-1;i=que[i].next)
    {
        int v=que[i].v;
        if(vis[v]) lca[que[i].id]=Find_set(v);
    }
}
void treedp(int u,int pre)
{
    for(int i=G[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v==pre) continue;
        treedp(v,u);
        dp[u]+=dp[v];
    }
}
void solve()
{
    memset(vis,false,sizeof(vis));
    dfs(1);
    for(int i=1;i<=m;i++)
    dp[lca[i]]-=2;
    treedp(1,-1);
    int ans=0;
    for(int i=2;i<=n;i++)
    {
        if(dp[i]==1) ans++;
        else if(!dp[i]) ans+=m;
    }
    printf("%d\n",ans);
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        input();
        solve();
    }
    return 0;
}

<think>好的,用户想学习树上差分,需要一份详细的中文学习材料,用Markdown格式。首先,我得先确认用户的基础,可能已经掌握了树的基本概念和遍历方法,以及差分数组的知识。接下来,我需要整理树上差分的核心内容,包括适用场景、基本思想、操作步骤、常见问题类型和例题解析。 首先,要介绍什么是树上差分,强调它用于高效处理树上路径更新和查询。然后,对比一维差分,说明其不同之处。接着,分点讲解点差分和差分的实现方法,包括思路、步骤和例子。这部分需要详细说明如何利用LCA来拆分路径,以及如何进行后续的DFS统计。 然后,要考虑用户可能遇到的常见问题,比如如何选择点差分还是差分,如何处理多次询问后的统计,以及时间复杂度的分析。这部分要简明扼要,帮助用户避免常见错误。 接下来是例题部分,选择经典题目如洛谷P3128和P3258,给出解题思路和代码示例,确保用户能通过实际应用加深理解。需要注意代码的注释和解释,帮助用户理解每一步的作用。 最后,推荐一些练习题目和参考资料,帮助用户巩固知识。需要确保链接的有效性,或者至少给出题目名称和来源,方便用户查找。 在组织内容时,要保持结构清晰,使用Markdown的标题、子标题、代码块和列表,让文档易读。同时,语言要简洁明了,避免过于学术化的表述,适合自学使用。还要注意检查逻辑是否连贯,步骤是否准确,避免技术错误,比如LCA的正确使用、差分数组的更新方式是否正确等。 可能用户需要的是从基础到应用的完整路径,因此需要按照学习顺序组织内容,先理论后实践,再练习题。另外,用户可能对树上差分的具体应用场景不太清楚,需要举例说明,比如统计节点被覆盖次数,或者路径上的权重更新。 需要确保提供的代码示例正确,并且有适当的注释,方便用户理解。同时,时间复杂度分析部分要简明,指出优化点,比如使用Tarjan或倍增法求LCA对效率的影响。 总结来说,整个学习材料需要覆盖概念、原理、实现步骤、例题详解和练习题,结构清晰,内容详实,适合自学。同时,语言要口语化,避免生硬,让用户容易理解和跟进。</think>```markdown # 树上差分 系统学习指南 ## 📚 前置知识要求 1. 树的基本概念(节点、、根、子树、LCA) 2.树的遍历方法(DFS、BFS) 3. 差分数组的基本概念与应用 4. 最近公共祖先(LCA)算法 ## 🌳 什么是树上差分? **核心思想**:将树上的路径修改操作转换为节点/的增量标记,通过DFS后序遍历进行统计 **典型应用场景**: - 统计树上路径覆盖次数 - 路径权重批量增加 - 网络流量统计 - 离线处理多个区间操作 ## 🔢 两种实现方式 ### 1. 点差分 **适用场景**:路径端点处理(统计节点被覆盖次数) **操作步骤**: 1. 找到路径u-v的LCA 2. 差分数组操作: ```python diff[u] += val diff[v] += val diff[lca] -= val if father[lca] exists: diff[father[lca]] -= val ``` ### 2. 差分 **适用场景**:路径处理(统计覆盖次数) **操作步骤**: 1. 将权下放到子节点 2. 差分数组操作: ```python diff[u] += val diff[v] += val diff[lca] -= 2*val ``` ## 🛠️ 实现流程 1. 预处理LCA(倍增/Tarjan) 2. 进行差分标记 3. DFS后序遍历统计结果 4. 根据需求处理最终数据 ## 💡 关键问题解析 ### Q1 如何选择点/差分? - 统计节点 → 点差分 - 统计差分 ### Q2 多次操作后如何统计? 通过DFS后序遍历累加子节点的标记值: ```python def dfs(u, parent): for v in children[u]: if v != parent: dfs(v, u) diff[u] += diff[v] ``` ## 📝 经典例题 ### 例题1:洛谷P3128 [USACO15DEC]Max Flow **题目**:给树上的多条路径,求最大节点流量 **解法**: 1. 点差分处理每条路径 2. DFS统计最终值 3. 遍历求最大值 **代码片段**: ```python # 伪代码示例 for _ in range(k): u, v = path lca = find_lca(u, v) diff[u] += 1 diff[v] += 1 diff[lca] -= 1 if parent[lca]: diff[parent[lca]] -= 1 dfs(root) print(max(diff)) ``` ### 例题2:洛谷P3258 [JLOI2014]松鼠的新家 **特点**:差分应用,注意首尾节点的特殊处理 ## 🚀 复杂度分析 - 空间复杂度:O(n) ## 🔍 推荐练习 1. [CF1076E] Vasya and a Tree 2. [LOJ #10131] 暗的连锁 3. [POJ 3417] Network ## 📖 学习资源推荐 1. 《算法竞赛进阶指南》第0x63节 2. OI Wiki 树上差分专题 3. 洛谷题单:树上差分经典问题 ## 🧠 学习要点总结 1. 差分思想的核心是"标记-传递" 2. LCA的正确求法是关键 3. 注意区分点差分和差分的不同处理 4. DFS后序遍历是统计的关键步骤 建议先理解一维差分,再通过2-3道典型题目实践,最后挑战变形题。遇到问题可画图模拟差分过程辅助理解。 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值