zoukankan      html  css  js  c++  java
  • [构造][LCT][并查集][CF1284F]New Year and Social Network

    Statement

    • 有两棵树 (T_1)(T_2),大小都是 (n)

    • 建立一个二分图,两部都有 (n-1) 个点

    • 如果树 (T_1) 去掉第 (i) 条边之后再加上 (T_2) 的第 (j) 条边得到的仍然是一棵树,则第一部的第 (i) 个点向第二部的第 (j) 个点连边

    • 求这个二分图的最大匹配并输出方案

    Solution

    • 我们大胆猜想:这个二分图有完美匹配

    • 这可以通过下面的具体构造得出,但这里还是说一说证明:

    • 根据霍尔 Hall 定理,一个二分图有完美匹配当且仅当对于其中一部的任意子集 (S),都满足点集 (S) 的邻居集合的大小不小于 (|S|)

    • 考虑第二部的一个子集 (S) 的邻居集合,把 (T_2) 上的边看作 (T_1) 上的路径,易得 (S) 的邻居集合就是 (S) 中路径经过所有边的并集

    • 显然对于这些边形成的每一个连通块,边集 (S) 都连通了这个连通块内的一部分点,并且不属于同一个连通块的两个点没有被边集 (S) 连通

    • 由于 (T_2) 是一棵树,所以每个连通块内路径的条数必然都小于这个连通块的点数,满足 Hall 定理,证毕

    • 现在考虑如何构造一组合法的匹配

    • 考虑从 (T_1) 中不断删叶子,假设某一次删掉了叶子 (u),与这个叶子相接的边为 (e)(e) 的另一端点为 (v)

    • 可以得出如果边 (e) 匹配了路径 ((u,x)),则 (T_2) 的变化为:

    • (1)删掉边 ((u,x))

    • (2)把所有包含 (u) 为端点的边都转接到 (v) 上面去,并把 (u) 删掉

    • 为保证 (T_2) 是一棵树,这个 (x) 必须是 (u)(v) 路径上的第二个点

    • 考虑使用 LCT 维护 (T_2) 的结构

    • 发现「把所有包含 (u) 为端点的边都转接到 (v) 上面去,并把 (u) 删掉」这个操作无法实现

    • 所以在这里我们采取的方法是维护一个并查集,在并查集上把 (u)(v) 并成一个点,而不是把 (u) 删掉

    • 这么处理之后找 (x) 就变成了找 (u)(v) 的路径上的第一条边,满足这条边的其中一端点(也就是 (x))和 (u) 不在同一个集合内

    • 可以在 Splay 上二分求得

    • (O(nlog n))

    Code

    #include <bits/stdc++.h>
    
    template <class T>
    inline void read(T &res)
    {
    	res = 0; bool bo = 0; char c;
    	while (((c = getchar()) < '0' || c > '9') && c != '-');
    	if (c == '-') bo = 1; else res = c - 48;
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = (res << 3) + (res << 1) + (c - 48);
    	if (bo) res = ~res + 1;
    }
    
    const int N = 25e4 + 5, M = N << 1;
    
    int n, ecnt, nxt[M], adj[N], go[M], a[N], b[N], ufs[N], fa[N], lc[N], rc[N],
    rev[N], len, que[N];
    
    void add_edge(int u, int v)
    {
    	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
    	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
    }
    
    int dfs(int u, int fu, int tot)
    {
    	for (int e = adj[u], v; e; e = nxt[e])
    		if ((v = go[e]) != fu) tot = dfs(v, u, tot), a[++tot] = v, b[tot] = u;
    	return tot;
    }
    
    int cx(int x)
    {
    	if (ufs[x] != x) ufs[x] = cx(ufs[x]);
    	return ufs[x];
    }
    
    void zm(int x, int y)
    {
    	if ((x = cx(x)) != (y = cx(y))) ufs[y] = x;
    }
    
    bool which(int x) {return rc[fa[x]] == x;}
    
    bool isRoot(int x)
    {
    	return lc[fa[x]] != x && rc[fa[x]] != x;
    }
    
    void down(int x)
    {
    	if (rev[x])
    	{
    		std::swap(lc[x], rc[x]);
    		if (lc[x]) rev[lc[x]] ^= 1;
    		if (rc[x]) rev[rc[x]] ^= 1;
    		rev[x] = 0;
    	}
    }
    
    void rotate(int x)
    {
    	int y = fa[x], z = fa[y], b = lc[y] == x ? rc[x] : lc[x];
    	if (!isRoot(y)) (lc[z] == y ? lc[z] : rc[z]) = x;
    	fa[x] = z; fa[y] = x; if (b) fa[b] = y;
    	if (lc[y] == x) rc[x] = y, lc[y] = b;
    	else lc[x] = y, rc[y] = b;
    }
    
    void splay(int x)
    {
    	que[len = 1] = x;
    	for (int y = x; !isRoot(y); y = fa[y]) que[++len] = fa[y];
    	for (int i = len; i >= 1; i--) down(que[i]);
    	while (!isRoot(x))
    	{
    		if (!isRoot(fa[x]))
    		{
    			if (which(x) ^ which(fa[x])) rotate(x);
    			else rotate(fa[x]);
    		}
    		rotate(x);
    	}
    }
    
    void access(int x)
    {
    	for (int y = 0; x; y = x, x = fa[x])
    	{
    		splay(x); rc[x] = y;
    		if (y) fa[y] = x;
    	}
    }
    
    void makeroot(int x)
    {
    	access(x); splay(x); rev[x] ^= 1;
    }
    
    void link(int x, int y)
    {
    	makeroot(x); fa[x] = y;
    }
    
    void cut(int x, int y)
    {
    	makeroot(x); access(y); splay(y);
    	lc[y] = fa[x] = 0;
    }
    
    int fp(int x, int y)
    {
    	makeroot(x); access(y); splay(y);
    	int pre, res, lst;
    	while (y)
    		if (down(lst = y), cx(x) == cx(y)) pre = y, y = rc[y];
    		else res = y, y = lc[y];
    	return splay(lst), printf("%d %d
    ", pre, res), res;
    }
    
    int main()
    {
    	int x, y;
    	read(n);
    	for (int i = 1; i < n; i++) read(x), read(y), add_edge(x, y);
    	dfs(1, 0, 0);
    	for (int i = 1; i <= n; i++) ufs[i] = i;
    	for (int i = 1; i < n; i++) read(x), read(y), link(x, y);
    	std::cout << n - 1 << std::endl;
    	for (int i = 1; i < n; i++)
    	{
    		printf("%d %d ", a[i], b[i]);
    		cut(a[i], fp(a[i], b[i])); link(a[i], b[i]);
    		zm(a[i], b[i]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    圆珠笔芯为什么那么细
    2017第45周二
    浅谈XXE攻击
    谈谈ssrf
    htop简介
    关闭火狐定期向“http://detectportal.firefox.com/”发包
    linux登录用户(终端)间的通信
    linux下ftp、telnet的安装和使用
    深入解析hostname
    supervisor启动sqlmapapi失败 sqlmapapi: ERROR (file is not executable)
  • 原文地址:https://www.cnblogs.com/xyz32768/p/12358575.html
Copyright © 2011-2022 走看看