• 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;
    }
    
  • 相关阅读:
    unomi漏洞复现
    xxl-job漏洞复现
    cgi漏洞复现
    celery漏洞复现
    bash漏洞复现
    学习ASP.NET的一些学习资源
    用EF DataBase First做一个简单的MVC3报名页面
    怎样在Word中插入代码并保持代码原始样式不变
    安装notepad++之后怎样在鼠标右键上加上Edit with notepad++
    安装Visual Studio 2010之后怎样安装MSDN Library
  • 原文地址:https://www.cnblogs.com/genshy/p/13388878.html
Copyright © 2020-2023  润新知