zoukankan      html  css  js  c++  java
  • UOJ#400. 【CTSC2018】暴力写挂

    传送门
    看到要求两棵树的 (lca) 深度不太好操作
    考虑枚举第二棵树的 (lca),这样剩下的都是只和第一棵树有关的
    而注意到 (dis(x,y)=d(x)+d(y)-2d(lca(x,y)))
    那么 (d(x)+d(y)-d(lca(x,y))=frac{1}{2}(dis(x,y)+d(x)+d(y)))
    这样就好多了,可以直接沿用WC2018通道的做法
    对于第一棵树进行边分治,把两边的集合合起来在第二棵树上建个虚树
    然后在虚树上枚举 (lca)(dp) 统计不属于同一个集合的答案
    复杂度 (Theta(nlog^2n)) 可以强行优化到 (nlogn)
    上述做法不够优美
    考虑另外一种做法,仍然是对于第一棵树进行边分治
    对于每个点,维护每一次边分治时的集合和到分治中心的距离
    那么只需要在第二棵树上枚举 (lca) 统计每次边分治时不同集合的最大的贡献就好了
    这个可以用线段树(二叉树)合并实现,(nlogn)
    然后考虑怎么维护每一次边分治时的集合和到分治中心的距离
    直接想法是直接暴力插入,(nlog^2n)
    然而边分治每次是分成两个集合,类似一个线段树的结构
    所以可以对于每个点维护一个线段树(二叉树),要保证树上每个点和是哪一次边分治对应
    只需要将这些点的树的当前点记录下来
    每次边分治的第一个集合向左跳,第二个向右跳就可以 (nlogn) 建树了
    复杂度 (Theta(nlogn))

    # include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    namespace IO {
    	const int maxn(1 << 21 | 1);
    
    	char ibuf[maxn], *iS, *iT, c;
    	int f;
    	
    	inline char Getc() {
    		return iS == iT ? (iT = (iS = ibuf) + fread(ibuf, 1, maxn, stdin), (iS == iT ? EOF : *iS++)) : *iS++;
    	}
    
    	template <class Int> inline void In(Int &x) {
    		for (c = Getc(), f = 1; c < '0' || c > '9'; c = Getc()) f = c == '-' ? -1 : 1;
    		for (x = 0; c >= '0' && c <= '9'; c = Getc()) x = (x << 1) + (x << 3) + (c ^ 48);
    		x *= f;
    	}
    }
    
    using IO :: In;
    
    const int maxn(4e5 + 5);
    const ll inf(1e18);
    
    int first1[maxn << 1], first2[maxn], cnt, head[maxn], m, n, fw[maxn << 1], tmp[maxn << 1], len;
    int size[maxn << 1], mn, rte, vis[maxn << 2], sz;
    int rt[maxn], tot, ls[maxn * 40], rs[maxn * 40], fa[maxn], fc[maxn];
    ll dis[maxn], ans, lmx[maxn * 40], rmx[maxn * 40];
    
    struct Edge {
    	int to, next, w;
    } edg[maxn << 1], edge1[maxn << 2], edge2[maxn << 1];
    
    inline void Addpre(int u, int v, int w) {
    	edg[cnt] = (Edge){v, head[u], w}, head[u] = cnt++;
    	edg[cnt] = (Edge){u, head[v], w}, head[v] = cnt++;
    }
    
    inline void Add1(int u, int v, int w) {
    	edge1[cnt] = (Edge){v, first1[u], w}, first1[u] = cnt++;
    	edge1[cnt] = (Edge){u, first1[v], w}, first1[v] = cnt++;
    }
    
    inline void Add2(int u, int v, int w) {
    	edge2[cnt] = (Edge){v, first2[u], w}, first2[u] = cnt++;
    	edge2[cnt] = (Edge){u, first2[v], w}, first2[v] = cnt++;
    }
    
    int Build(int l, int r) {
    	if (l > r) return 0;
    	if (l == r) return tmp[l];
    	int cur, ls, rs, mid;
    	cur = ++m, mid = (l + r) >> 1;
    	ls = Build(l, mid), rs = Build(mid + 1, r);
    	if (ls) Add1(cur, ls, fw[ls]);
    	if (rs) Add1(cur, rs, fw[rs]);
    	return cur;
    }
    
    void Rebuild(int u, int ff) {
    	int e, v, ls, rs, mid;
    	len = 0;
    	for (e = head[u]; ~e; e = edg[e].next)
    		if ((v = edg[e].to) != ff) fw[v] = edg[e].w, tmp[++len] = v;
    	mid = (len + 1) >> 1;
    	ls = Build(1, mid), rs = Build(mid + 1, len);
    	if (ls) Add1(u, ls, fw[ls]);
    	if (rs) Add1(u, rs, fw[rs]);
    	for (e = head[u]; ~e; e = edg[e].next)
    		if ((v = edg[e].to) != ff) dis[v] = dis[u] + fw[v], Rebuild(v, u);
    }
    
    void Getroot(int u, int ff, int fe) {
    	int e, v;
    	size[u] = 1;
    	for (e = first1[u]; ~e; e = edge1[e].next)
    		if ((v = edge1[e].to) != ff && !vis[e]) {
    			Getroot(v, u, e);
    			size[u] += size[v];
    		}
    	if (abs(sz - size[u] * 2) < mn) mn = abs(sz - size[u] * 2), rte = fe;
    }
    
    void Dfs1(int u, int ff, int type, ll d) {
    	if (u <= n) {
    		++tot, lmx[tot] = rmx[tot] = -inf;
    		if (fa[u]) fc[u] ? rs[fa[u]] = tot : ls[fa[u]] = tot;
    		else rt[u] = tot;
    		fa[u] = tot, fc[u] = type;
    		type ? rmx[tot] = d + dis[u] : lmx[tot] = d + dis[u];
    	}
    	int e, v;
    	for (e = first1[u]; ~e; e = edge1[e].next)
    		if ((v = edge1[e].to) != ff && !vis[e]) Dfs1(v, u, type, d + edge1[e].w);
    }
    
    void Solve(int nrt) {
    	if (nrt == -1 || vis[nrt]) return;
    	int rt1, rt2, tmp1, tmp2;
    	rt1 = edge1[nrt].to, rt2 = edge1[nrt ^ 1].to;
    	vis[nrt] = vis[nrt ^ 1] = 1;
    	tmp1 = size[rt1] > size[rt2] ? sz - size[rt2] : size[rt1];
    	tmp2 = size[rt2] > size[rt1] ? sz - size[rt1] : size[rt2];
    	Dfs1(rt1, rt2, 0, edge1[nrt].w), Dfs1(rt2, rt1, 1, 0);
    	sz = tmp1, mn = m + 1, Getroot(rt1, rt2, -1), Solve(rte);
    	sz = tmp2, mn = m + 1, Getroot(rt2, rt1, -1), Solve(rte);
    }
    
    int Merge(int x, int y, ll del) {
    	if (!x || !y) return x | y;
    	ans = max(ans, lmx[x] + rmx[y] - del);
    	ans = max(ans, lmx[y] + rmx[x] - del);
    	lmx[x] = max(lmx[x], lmx[y]), rmx[x] = max(rmx[x], rmx[y]);
    	ls[x] = Merge(ls[x], ls[y], del), rs[x] = Merge(rs[x], rs[y], del);
    	return x;
    }
    
    void Dfs2(int u, int ff, ll d) {
    	int e, v;
    	ans = max(ans, dis[u] * 2 - d * 2);
    	for (e = first2[u]; ~e; e = edge2[e].next)
    		if ((v = edge2[e].to) != ff) {
    			Dfs2(v, u, d + edge2[e].w);
    			rt[u] = Merge(rt[u], rt[v], d + d);
    		}
    }
    
    int main() {
    	int i, u, v, w;
    	memset(head, -1, sizeof(head));
    	memset(first1, -1, sizeof(first1));
    	memset(first2, -1, sizeof(first2));
    	ans = -inf, In(n), m = n;
    	for (i = 1; i < n; ++i) In(u), In(v), In(w), Addpre(u, v, w);
    	cnt = 0;
    	for (i = 1; i < n; ++i) In(u), In(v), In(w), Add2(u, v, w);
    	cnt = 0, Rebuild(1, 0);
    	sz = m, mn = m + 1, Getroot(1, 0, -1);
    	Solve(rte), Dfs2(1, 0, 0);
    	printf("%lld
    ", ans >> 1);
        return 0;
    }
    
  • 相关阅读:
    如何运用领域驱动设计
    如何运用领域驱动设计
    linq Select与SelectMany的区别
    linq GroupBy 多字段分组
    Enumerable<T>的GroupJoin 方法和Join的用法和区别
    使用vscode搭建vue开发环境
    C# Windows服务发布生成步骤
    C#使用表驱动算法实现多层if或者switch语句重构
    记一次服务器重启后应用程序启动出错
    项目中的NLog日志记录
  • 原文地址:https://www.cnblogs.com/cjoieryl/p/10229651.html
Copyright © 2011-2022 走看看