P2783 有机化学之神偶尔也会作弊
前言
这道题以前还是道(水)黑题,现在怎么降紫了????
前置芝士
-
tarjain 缩点
-
倍增求LCA或树剖求LCA
-
脑子。。。
题意
给你一个无向图,要求你把所有的环缩成一个点。在新得到的图上问你两个点之间有多少个点。
分析
首先我们会由 "所有的环状碳都变成了一个碳" 想到要缩点。
但是无向图怎么缩点呢?
我们可以按照原来无向图那样缩点,但要注意的一点是 $to != fa[x] $
因为这是无向图,可能有的边会直接连向他父亲,假如我们要走这条边的话,就会重
复搜,就这样一直无限循环下去。剩下的就和有向图的缩点没什么区别了。
接着我们就要考虑每个询问。
我们把所有的环去掉后,就会得到一个有向无环图(树)。不理解的童鞋请画图自证
那么问题就会转化为树上问题。
甩给你一张图
假如我们要求 \(4\) 和 \(7\) 之间的有多少个点。我么可以用
\(dep[4] + dep[7] - 2 * dep[3]\) + 1
即 \(dep[x] + dep[y] - 2 * dep[lca(x,y)] + 1\)
由于他的深度有类似于前缀和的性质,所以我们可以这么处理。
为什么要减一呢? 因为你 $LCA $处 只能算一个点,但你却减了两次,所以要
重新加上
补充
关于一个数转二进制的方法。
我们可以联想到快速幂中要依次取出指数的二进制每一位,所以我们可以像快速幂
中的写法模拟出二进制每一位。
代码如下
void shuchu(int x)
{
int xx = 0;//记录有多少位
for(; x; x>>=1)//依次取出每一位上的数字
{
xx++;
if(x & 1) t[xx] = 1;
else t[xx] = 0;
}
for (int i = xx; i >= 1; i-- ) printf("%d",t[i]);//倒序输出
printf("\n");
}
几个要注意的点
-
求两个点的LCA 一定要在新建的图上求 (本蒟蒻就在这里卡了好几回)
-
树剖求 LCA 时要注意是在缩完点之后的图上求
-
tarjain 缩点时要注意不能访问到他父亲的边
不懂得同鞋 , 请看下面代码 ,下面有注释。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5+10;
int n,m,u,v,x,y,tot,sum,cnt,num,topp,T;
int dep[N],fa[N],size[N],top[N],head[N],hed[N];
int shu[N],dfn[N],low[N],sta[N],son[N];
int t[N];
bool vis[N];
inline int read()//标准快读
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
return s * w;
}
struct node{int to,net;}e[N<<1],edge[N<<1];//为了压行不择手段
void add(int x,int y)
{
e[++tot].to = y;
e[tot].net = head[x];
head[x] = tot;
}
void add_(int x,int y)//建新图上的边
{
edge[++sum].to = y;
edge[sum].net = hed[x];
hed[x] = sum;
}
void tarjain(int x,int fa)//缩点
{
dfn[x] = low[x] = num++;
sta[++topp] = x; vis[x] = 1;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa) continue;//特判是不是联向他父亲得边
if(!dfn[to])
{
tarjain(to,x);
low[x] = min(low[x],low[to]);
}
else if(vis[to])
{
low[x] = min(low[x],dfn[to]);
}
}
if(dfn[x] == low[x])//求强联通分量
{
cnt++; int y;
do
{
y = sta[topp--];
//size[cnt]++;
shu[y] = cnt;
vis[y] = 0;
}while(x != y);
}
}
void get_tree(int x)//树剖第一遍DFS求重儿子
{
dep[x] = dep[fa[x]] + 1; size[x] = 1;
for(int i = hed[x]; i; i = edge[i].net)
{
int to = edge[i].to;
if(to == fa[x]) continue;
fa[to] = x;
get_tree(to);
size[x] += size[to];
if(size[son[x]] > size[to]) son[x] = to;
}
}
void dfs(int x,int topp)//树剖第二遍DFS求每条链的顶端
{
top[x] = topp;
if(son[x]) dfs(son[x],topp);
for(int i = hed[x]; i; i = edge[i].net)
{
int to = edge[i].to;
if(to == fa[x] || to == son[x]) continue;
dfs(to,to);
}
}
int lca(int x,int y)//树剖求LCA
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
x = fa[top[x]];
}
if(dep[x] < dep[y]) return x;
else return y;
}
void shuchu(int x)//二进制转化
{
int xx = 0;
for(; x; x>>=1)
{
xx++;
if(x & 1) t[xx] = 1;
else t[xx] = 0;
}
for (int i = xx; i >= 1; i--) printf("%d",t[i]);
printf("\n");
}
int main()
{
n = read(); m = read();
for(int i = 1; i <= m; i++)
{
u = read(); v = read();
add(u,v); add(v,u);
}
for(int i = 1; i <= n; i++)
{
if(!dfn[i]) tarjain(i,0);
}
for(int i = 1; i <= n; i++)//缩点
{
for(int j = head[i]; j; j = e[j].net)
{
int to = e[j].to;
if(shu[to] != shu[i])
{
add_(shu[i],shu[to]);
}
}
}
get_tree(1);
dfs(1,1);
T = read();
while(T--)
{
x = read(); y = read();
int Lca = lca(shu[x],shu[y]);
int ans = dep[shu[x]] + dep[shu[y]] - 2 * dep[Lca] + 1;//计算每个询问的答案
shuchu(ans);
}
return 0;
}