P2783 有机化学之神偶尔也会作弊

题面

前言

这道题以前还是道()黑题,现在怎么降紫了????

前置芝士

  1. tarjain 缩点

  2. 倍增求LCA或树剖求LCA

  3. 脑子。。。

题意

给你一个无向图,要求你把所有的环缩成一个点。在新得到的图上问你两个点之间有多少个点。

分析

首先我们会由 "所有的环状碳都变成了一个碳" 想到要缩点。

但是无向图怎么缩点呢?

我们可以按照原来无向图那样缩点,但要注意的一点是 $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");
}

几个要注意的点

  1. 求两个点的LCA 一定要在新建的图上求 (本蒟蒻就在这里卡了好几回)

  2. 树剖求 LCA 时要注意是在缩完点之后的图上求

  3. 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;
}
posted @ 2020-07-28 07:15  genshy  阅读(172)  评论(2编辑  收藏  举报