推销博客 https://www.cnblogs.com/Tyouchie/
题目描述
传说中的暗之连锁被人们称为 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)
C++ 代码
#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;
}
这里是分类加法原理,不是分步乘法原理
他说了1是跟节点吗?
没有根节点随便指定一个根节点呗,不影响最后的答案。而且推导的时候也是随便指定一个根节点,只要逻辑正确了就没错了。
时间复杂度要加个log吧?
log是常数有道理为什么最后统计的时候 $n$ 是从 $2$ 开始
因为1没边的
边查分,他这边是下放到下面的那个点上,用点来表示这个边的
这句话:对于只被覆盖一次的边,第二次我们只能切断(x,y)边,方法唯一;
这题目好像没说只有切不同附加边才算不同方案啊,切不同主要边也算一种方案把。。
所以dfs里为啥是加等于捏
你只能切一次 树边 和 一次 非树边,你这样的话就切了两次树边了