树上差分:闇の連鎖 题解

题目描述

传说中的暗之连锁被人们称为 Dark。

Dark 是人类内心的黑暗的产物,古今中外的勇者们都试图打倒它。

经过研究,你发现 Dark 呈现无向图的结构,图中有 N 个节点和两类边,一类边被称为主要边,而另一类被称为附加边。

Dark 有 N – 1 条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径。

另外,Dark 还有 M 条附加边。

你的任务是把 Dark 斩为不连通的两部分。

一开始 Dark 的附加边都处于无敌状态,你只能选择一条主要边切断。

一旦你切断了一条主要边,Dark 就会进入防御模式,主要边会变为无敌的而附加边可以被切断。

但是你的能力只能再切断 Dark 的一条附加边。

现在你想要知道,一共有多少种方案可以击败 Dark。

注意,就算你第一步切断主要边之后就已经把 Dark 斩为两截,你也需要切断一条附加边才算击败了 Dark。
输入格式

第一行包含两个整数 N 和 M。

之后 N – 1 行,每行包括两个整数 A 和 B,表示 A 和 B 之间有一条主要边。

之后 M 行以同样的格式给出附加边。

输出格式
输出一个整数表示答案。

数据范围
N≤100000,M≤200000,数据保证答案不超过231−1

样例
输入样例:
4 1
1 2
2 3
1 4
3 4
输出样例:
3

在没有附加边的情况下,我们发现这是一颗树,那么再添加条附加边(x,y)后,会造成(x,y)之间产生一个环
如果我们第一步截断了(x,y)之间的一条路,那么我们第二次只能截掉(x,y)之间的附加边,才能使其不连通;
我们将每条附加边(x,y)称为将(x,y)之间的路径覆盖了一遍;
因此我们只需要统计出每条主要边被覆盖了几次即可;
对于只被覆盖一次的边,第二次我们只能切断(x,y)边,方法唯一;
如果我们第一步切断了被覆盖0次的边,那么我们已经将其分为两部分,那么第二部只需要在m条附加边中任选一条即可,如果第一步截到被覆盖超过两次的边,将无法将其分为两部分;
运用乘法原理,我们累加答案;
那么怎么标记我们的边(x,y)被覆盖了几次呢,那么我们可以使用树上差分,是解决此类问题的经典套路;
我们想,对于一条边(x,y),我们添加一条边;
那么只会对x到lca(x,y)到y上的边产生影响,对于(x,y)我们将x节点的权值+1,y节点的权值+1,另lca(x,y)的权值-2,画图很好理解,那么我们进行一遍dfs求出每个节点权值,那么这个值就是节点父节点连边被覆盖的次数,按上述方法累加答案即可;
时间复杂度分析:O(N+M)

#include<bits/stdc++.h>
using namespace std;
#define N 500100

int x,y,n,m,tot,ans,p[N],f1[N],lin[N],f[N][25],d[N],vis[N];

template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))  {if(ch==‘-‘)  f=-1;  ch=getchar();}
    while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
    x*=f;
}

struct gg {
    int x,y,next;
}a[N<<1];

inline void add(int x,int y) {
    a[++tot].x=x;
    a[tot].y=y;
    a[tot].next=lin[x];
    lin[x]=tot;
}

void bfs(int x) {
    queue<int> q;
    q.push(1);
    d[1]=1;
    while(q.size()) {
        int x=q.front();
        q.pop();
        for(int i=lin[x];i;i=a[i].next) {
            int y=a[i].y;
            if(d[y]) continue;
            d[y]=d[x]+1;
            f[y][0]=x;
            for(int j=1;j<=23;j++){
                f[y][j]=f[f[y][j-1]][j-1];
            }
            q.push(y);
        }
    }
}

inline int lca(int x,int y) {
    if(d[x]>d[y]) swap(x,y);
    for(int i=23;i>=0;i--) {
        if(d[f[y][i]]>=d[x]) y=f[y][i];
    }
    if(x==y) return x;
    for(int i=23;i>=0;i--) {
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    }
    return f[x][0];
}

inline void dfs(int x) {
    vis[x]=1;
    for(int i=lin[x];i;i=a[i].next) {
        int y=a[i].y;
        if(vis[y]) continue;
        dfs(y);
        p[x]+=p[y];
    }
}

int main() {
    read(n); read(m);
    int x,y;
    for(int i=1;i<n;i++) {
        read(x);read(y);
        add(x,y);
        add(y,x);
    }
    bfs(1);
    for(int i=1;i<=m;i++) {
        read(x);read(y);
        p[x]++,p[y]++;
        p[lca(x,y)]-=2;
    }
    dfs(1);
    for(int i=2;i<=n;i++) {
        if(!p[i]) ans+=m;
        if(p[i]==1) ans+=1;
    }
    cout<<ans<<endl;
    return 0;
}

原文地址:https://www.cnblogs.com/Tyouchie/p/11030221.html

时间: 2024-10-18 19:24:37

树上差分:闇の連鎖 题解的相关文章

poj3417 闇の連鎖 【树上差分】By cellur925

闇の連鎖(yam.pas/c/cpp)题目描述传说中的暗之连锁被人们称为 Dark.Dark 是人类内心的黑暗的产物,古今中外的勇者们都试图打倒它.经过研究,你发现 Dark 呈现无向图的结构,图中有 N 个节点和两类边,一类边被称为主要边,而另一类被称为附加边.Dark 有 N – 1条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径.另外,Dark 还有 M 条附加边.你的任务是把 Dark 斩为不连通的两部分.一开始 Dark的附加边都处于无敌状态,你只能选择一条主

AcWing352 闇の連鎖(树上差分+lca)

这道题的我们知道如果在两个点之间有附加边,其实就相当于在这个回路上的每条边都权值+1,这样就可以通过差分数组来快速求取大小 这里的精髓就是在输入的两个位置+1,而在他们的lca上-=2: #include<iostream> #include<queue> #include<map> #include<vector> #include<cstdio> #include<algorithm> #include<stack>

BZOJ 3631 [JLOI2014]松鼠的新家 | 树上差分

链接 BZOJ 3631 题解 看起来是树剖?实际上树上差分就可以解决-- 当要给一条路径(u, v) +1的时候,给d[u] += 1, d[v] += 1, d[lca(u, v)] -= 1, d[fa[lca(u, v)]] -= 1. 注意这道题中路径的终点是不 +1的. #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #include <q

NOIp2015 运输计划 [LCA] [树上差分] [二分答案]

我太懒了 吃掉了题面 题解 & 吐槽 一道很好的树上差分练习题. 不加fread勉强a过bzoj和luogu的数据,加了fread才能在uoj里卡过去. 可以发现,答案则是运输计划里花费的最大值,最大值最小,便是二分答案的标志. 那么该怎么check呢... 我们得找出所有超过限制的计划,这个过程可以在LCA倍增的过程中预处理出来. 然后再找出一些被这些计划都覆盖的边,找到最大的那条边,如果最大的计划花费减去最大的那条边小于x,那么x就是可行的. 但是该怎么找到那些被计划都覆盖的边呢... 我们

树上差分总结

最近翻了一下自己的U盘,突然发现有一个树上差分的ppt,居然还没有学,因为以前以为这个很高大尚,以为很难.......(其实真的不难) 好吧,入正题 首先得知道差分这个东西吧!简单差分 在讲树上差分之前,首先需要知道树的以下两个性质:(1)任意两个节点之间有且只有一条路径.(2)一个节点只有一个父亲节点(即只有一条返祖边) 可以发现所有树上两点 a,b 的路径可拆为:a--->LCA(a,b),LCA(a,b)--->b(最好去学一下LCA的快速求法(这个我没写,随便上网找了一个感觉比较好的:

差分 and 树上差分

差分数组 定义 百度百科中的差分定义 //其实这完全和要讲的没关系 qwq 进去看了之后是不是觉得看不懂? 那我简单概括一下qwq 差分数组de定义:记录当前位置的数与上一位置的数的差值. 栗子 容易发现的是,\(\sum_{1}^{i}{b_i}\)即代表\(a_i\) 的值. \((\sum_{1}^{i}\) 即代表从1累加到i.) 思想 看到前面的\(\sum_{1}^{i}\) 你一定会发现这是前缀和! 那你认为这是前缀和? 的确是qwq. 实际上这并不是真正意义上的前缀和. 前缀和的

Codechef Counting the important pairs 树上差分

传送门 题意:给出一个$N$个点.$M$条边的无向连通图,求有多少组无序数对$(i,j)$满足:割掉第$i$条边与第$j$条边之后,图变为不连通.$N \leq 10^5 , M \leq 3 \times 10^5$ 竟然随机化,歪果仁的思想好灵活qwq肯定是数据结构做多了 看起来很像割边,考虑$tarjan$,但是边散连通分量并不是很好实现,而且有很多特殊情况需要判断,所以我们考虑另外的算法 考虑$tarjan$时建出的一棵树.对于它来说,在一个端点在其下方.另一个端点在其上方的的返祖边可以

差分数组 and 树上差分

差分数组 定义 百度百科中的差分定义 //其实这完全和要讲的没关系 qwq 进去看了之后是不是觉得看不懂? 那我简单概括一下qwq 差分数组de定义:记录当前位置的数与上一位置的数的差值. 栗子 容易发现的是,\(\sum_{j=1}^{i} b_j\)即代表\(a_i\) 的值. \((\sum\) 即代表累加.) 思想 看到前面的\(\sum\) 你一定会发现这是前缀和! 那你认为这是前缀和? 的确是qwq. 实际上这并不是真正意义上的前缀和. 前缀和的思想是 根据元素与元素之间的并集关系(

[GX/GZOI2019]旧词(树上差分+树剖+线段树)

考虑k=1的做法:这是一道原题,我还写过题解,其实挺水的,但当时我菜还是看题解的:https://www.cnblogs.com/hfctf0210/p/10187947.html.其实就是树上差分后值为1. 考虑k>1的做法:其实可以再次树上差分,给每个点i赋值v[i]=dep[i]k-dep[i-1]k,然后还是和原来一样开一棵线段树,记录一个val[rt]表示当前节点内区间v值的和,以及sum[rt]表示区间值.修改时打标记,只需要将sum[rt]+=v*val[rt],lazy[rt]+