• 圆方树学习笔记


    写这个东西只是记录一下我学过圆方树 ( ext{/cy})

    建树

    圆方树是一种将图变成树的方法。

    首先,把原图中的所有点都看成圆点,我们需要求出图中所有的点双连通分量,可以使用 Tarjan 算法。

    然后,在每一个点双连通分量中间建立一个方点,将此点双连通分量中的所有点向这个方点连边。

    这样就可以把图转成树,利用一些树上的性质解题了。

    放一张来自 WC PPT 的图。

    代码:

    int dfn[N], low[N], tim, stk[N], tp, cnt;
    int tot, head[N], headc[N], ver[M], nxt[M];
    //headc: 原图   head: 圆方树
    
    inline void add(int h[], int u, int v)
    {
    	ver[++tot] = v, nxt[tot] = h[u], h[u] = tot;
    }
    
    void Tarjan(int u)
    {
    	dfn[u] = low[u] = ++tim, stk[++tp] = u;
    	for (int i = headc[u]; i; i = nxt[i])
    	{
    		int v = ver[i];
    		if (!dfn[v])
    		{
    			Tarjan(v);
    			low[u] = min(low[u], low[v]);
    			if (low[v] == dfn[u])
    			{
    				++cnt;
    				int y = -1;
    				do
    				{
    					y = stk[tp--];
    					add(head, y, cnt), add(head, cnt, y);
    				} while (y != v);
    				add(head, cnt, u), add(head, u, cnt);
    			}
    		}
    		else low[u] = min(low[u], dfn[v]);
    	}
    }
    
    //主函数
    int main()
    {
        n = gi <int> (), m = gi <int> ();
        for (int i = 1; i <= m; i+=1)
        {
            int u = gi <int> (), v = gi <int> ();
            add(headc, u, v), add(headc, v, u);
        }
        cnt = n;
        for (int i = 1; i <= n; i+=1)
            if (!dfn[i]) Tarjan(i), --tp;
    }
    

    性质

    1. 圆方数上原点和方点交替出现。
    2. 圆方数的点数小于 (2n),因此做题时注意开两倍空间。
    3. 圆方树上所有不是叶子节点的圆点都是原图中的一个割点。

    应用

    道路相遇

    题面

    题意:

    (q) 次询问,每次询问点 (u) 到点 (v) 所有简单路径的交。

    建出圆方树,问题就转换成圆方树上点 (u) 与点 (v) 之间圆点的个数。

    记录一下树上前缀和,树上差分即可。

    代码:

    #include <bits/stdc++.h>
    #define DEBUG fprintf(stderr, "Passing [%s] line %d
    ", __FUNCTION__, __LINE__)
    #define File(x) freopen(x".in","r",stdin); freopen(x".out","w",stdout)
    
    using namespace std;
    
    typedef long long LL;
    typedef pair <int, int> PII;
    typedef pair <int, PII> PIII;
    
    template <typename T>
    inline T gi()
    {
    	T f = 1, x = 0; char c = getchar();
    	while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
    	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    	return f * x;
    }
    
    const int INF = 0x3f3f3f3f, N = 1000003, M = N << 2;
    
    int n, m, q;
    int tot, head[N], headc[N], ver[M], nxt[M];
    int dfn[N], low[N], tim, stk[N], tp, cnt;
    int sum[N];
    
    inline void add(int h[], int u, int v)
    {
    	ver[++tot] = v, nxt[tot] = h[u], h[u] = tot;
    }
    
    void Tarjan(int u)
    {
    	dfn[u] = low[u] = ++tim, stk[++tp] = u;
    	for (int i = headc[u]; i; i = nxt[i])
    	{
    		int v = ver[i];
    		if (!dfn[v])
    		{
    			Tarjan(v);
    			low[u] = min(low[u], low[v]);
    			if (low[v] == dfn[u])
    			{
    				++cnt;
    				int y = -1;
    				do
    				{
    					y = stk[tp--];
    					add(head, y, cnt), add(head, cnt, y);
    				} while (y != v);
    				add(head, cnt, u), add(head, u, cnt);
    			}
    		}
    		else low[u] = min(low[u], dfn[v]);
    	}
    }
    
    void dfs(int u, int f)
    {
    	sum[u] = (u <= n) + sum[f];
    	for (int i = head[u]; i; i = nxt[i])
    	{
    		int v = ver[i];
    		if (v == f) continue;
    		dfs(v, u);
    	}
    }
    
    int dep[N], fa[N], sz[N], son[N], topp[N];
    
    void dfs1(int u, int f)
    {
    	dep[u] = dep[f] + 1, fa[u] = f, sz[u] = 1;
    	for (int i = head[u]; i; i = nxt[i])
    	{
    		int v = ver[i];
    		if (v == f) continue;
    		dfs1(v, u);
    		sz[u] += sz[v];
    		if (sz[v] > sz[son[u]]) son[u] = v;
    	}
    }
    
    void dfs2(int u, int f)
    {
    	topp[u] = f;
    	if (!son[u]) return;
    	dfs2(son[u], f);
    	for (int i = head[u]; i; i = nxt[i])
    	{
    		int v = ver[i];
    		if (v == fa[u] || v == son[u]) continue;
    		dfs2(v, v);
    	}
    }
    
    inline int LCA(int u, int v)
    {
    	while (topp[u] != topp[v])
    	{
    		if (dep[topp[u]] < dep[topp[v]]) swap(u, v);
    		u = fa[topp[u]];
    	}
    	if (dep[u] < dep[v]) return u;
    	return v;
    }
    
    int main()
    {
    	//File("");
    	n = gi <int> (), m = gi <int> ();
    	for (int i = 1; i <= m; i+=1)
    	{
    		int u = gi <int> (), v = gi <int> ();
    		add(headc, u, v), add(headc, v, u);
    	}
    	cnt = n;
    	Tarjan(1);
    	dfs(1, 0);
    	q = gi <int> ();
    	dfs1(1, 0); dfs2(1, 1);
    	while (q--)
    	{
    		int u = gi <int> (), v = gi <int> ();
    		int lca = LCA(u, v);
    		printf("%d
    ", sum[u] + sum[v] - sum[lca] - sum[fa[lca]]);
    	}
    	return 0;
    }
    

    APIO2018 铁人两项

    题面

    题意:

    问有多少组 (s)(c)(f),满足存在从 (s)(c) 和从 (c)(f) 的简单路径。

    首先介绍一个点双的性质:对于一个点双中的两个点 (u)(v),它们之间简单路径的并集恰好等于这个点双。

    然后问题就转换成了:固定 (s)(f),问有多少个合法的 (c)

    考虑圆方树上两圆点在原图中所有简单路径的并,将这个问题转换到圆方树上就变成 两圆点之间的路径路径上方点所在点双中的所有点

    这个问题很好求解,我们把圆方树上每个方点的权值设为这个点双中的点数,圆点的权值设为 (-1),答案即为圆方树上 (sum) 两点之间所有点的权值之和。

    代码:

    #include <bits/stdc++.h>
    #define DEBUG fprintf(stderr, "Passing [%s] line %d
    ", __FUNCTION__, __LINE__)
    #define File(x) freopen(x".in","r",stdin); freopen(x".out","w",stdout)
    
    using namespace std;
    
    typedef long long LL;
    typedef pair <int, int> PII;
    typedef pair <int, PII> PIII;
    typedef pair <LL, int> PLI;
    
    template <typename T>
    inline T gi()
    {
    	T f = 1, x = 0; char c = getchar();
    	while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
    	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    	return f * x;
    }
    
    const int INF = 0x3f3f3f3f, N = 200003, M = N << 3;
    
    int n, m;
    int tot, head[N], headc[N], ver[M], nxt[M];
    int dfn[N], low[N], tim, stk[N], tp;
    int val[N], sz[N];
    int cnt;
    LL ans;
    int num;
    
    inline void add(int h[], int u, int v)
    {
    	ver[++tot] = v, nxt[tot] = h[u], h[u] = tot;
    }
    
    void Tarjan(int u)
    {
    	++num;
    	dfn[u] = low[u] = ++tim, stk[++tp] = u;
    	for (int i = head[u]; i; i = nxt[i])
    	{
    		int v = ver[i];
    		if (!dfn[v])
    		{
    			Tarjan(v);
    			low[u] = min(low[u], low[v]);
    			if (low[v] == dfn[u])
    			{
    				val[++cnt] = 0;
    				int y = -1;
    				do
    				{
    					y = stk[tp--];
    					add(headc, y, cnt), add(headc, cnt, y);
    					++val[cnt];
    				} while (y != v);
    				add(headc, u, cnt), add(headc, cnt, u);
    				++val[cnt];
    			}
    		}
    		else low[u] = min(low[u], dfn[v]);
    	}
    }
    
    void dfs(int u, int f, int mn)
    {
    	sz[u] = (u <= n);
    	for (int i = headc[u]; i; i = nxt[i])
    	{
    		int v = ver[i];
    		if (v == f) continue;
    		dfs(v, u, mn);
    		ans += (LL)2 * val[u] * sz[u] * sz[v];
    		sz[u] += sz[v];
    	}
    	ans += (LL)2 * val[u] * sz[u] * (mn - sz[u]);
    }
    
    int main()
    {
    	//File("");
    	n = gi <int> (), m = gi <int> ();
    	for (int i = 1; i <= m; i+=1)
    	{
    		int u = gi <int> (), v = gi <int> ();
    		add(head, u, v), add(head, v, u);
    	}
    	for (int i = 1; i <= n * 2; i+=1) val[i] = -1;
    	cnt = n;
    	for (int i = 1; i <= n; i+=1)
    		if (!dfn[i])
    		{
    			num = 0;
    			Tarjan(i);
    			--tp;
    			dfs(i, 0, num);
    		}
    	printf("%lld
    ", ans);
    	return 0;
    }
    

    参考学习

    1. https://www.cnblogs.com/PinkRabbit/p/10446473.html
    2. https://www.cnblogs.com/cjyyb/p/9098400.html
  • 相关阅读:
    4-8 求二叉树高度 (20分)
    汉诺塔的递归和非递归实现
    5-18 银行业务队列简单模拟 (25分)
    ACM 刷题小技巧【转】
    5-21 求前缀表达式的值(25分)
    5-20 表达式转换 (25分)
    约瑟夫环----循环链表问题
    关于埃拉托色尼筛选法的整理(质数问题)
    编码---隐藏在计算机软硬件背后的语言
    内排序和外排序扫盲
  • 原文地址:https://www.cnblogs.com/xsl19/p/13513635.html
Copyright © 2020-2023  润新知