Loading

【题解】P6199 [EER1]河童重工

东方吧有个老哥上网搜河童重工结果出来这个题,炸出来一车 OIer,笑死我了 /kx

点分治太玄学了吧,正确写法到底是什么???

题意

给定两棵树 \(T_1, T_2\),结点一一对应,在结点 \(u, v\) 之间连边的代价是它们在 \(T_1, T_2\) 上的树上路径长度之和。求结点两两连边形成的完全图的最小生成树权值。

\(2 \leq n \leq 10^5\)

思路

点分治 + 虚树。

首先套路地考虑在 \(T_2\) 上点分治,钦定有效路径过重心。

然后令 \(dep_u\) 表示结点 \(u\) 到分治重心的距离,那么在 \(u, v\) 之间连边的距离就是 \(dep_u + dep_v + T_1.dis(u, v)\),就变成了 AT3611.

这里暴力算 \(dis\) 的复杂度是错的,考虑先对当前分治重心的子树在 \(T_1\) 上建一棵虚树。

具体考虑的是把每个结点 \(u\) 拆成两份 \(u, u^{\prime} = u + n\),连长度为 \(dep_u\) 的边。令 \(u^{\prime}\) 为虚点。

考虑在虚树上跑一遍 dijkstra 求出距离每个结点 \(u\) 最近的虚点,记为 \(pre_u\)。考虑对于虚树上的每一条边 \((u, v)\),在 \(pre_u, pre_v\) 之间连一条长度为 \(dis_u + dis_v + w(u, v)\) 的边,其中 \(dis_u, dis_v\) 表示 \(u, v\) 到最近虚点的距离,\(w(u, v)\) 表示 \((u, v)\) 的边权。

实际上等价于钦定每一条边算它的贡献,这里感觉有点像边分治做这类套路的时候在虚树上枚举 LCA,但是具体不太会证。

然后跑一遍 kruskal 就行。

时间复杂度 \(O(n \log^2 n)\)

代码

#include <cstdio>
#include <vector>
#include <queue>
#include <iostream>
#include <algorithm>
using namespace std;

#define int long long

const int maxn = 6e5 + 5;
const int lg_sz = 20;
const int nd_sz = maxn * lg_sz;
const int inf = 1e9;

int n;

namespace IO
{
    //by cyffff
	int len = 0;
	char ibuf[(1 << 20) + 1], *iS, *iT, out[(1 << 26) + 1];
	#define gh() (iS == iT ? iT = (iS = ibuf) + fread(ibuf, 1, (1 << 20) + 1, stdin), (iS == iT ? EOF : *iS++) : *iS++)
	#define reg register

	inline int read()
    {
		reg char ch = gh();
		reg int x = 0;
		reg char t = 0;
		while (ch < '0' || ch > '9') t |= ch == '-', ch = gh();
		while (ch >= '0' && ch <= '9') x = x * 10 + (ch ^ 48), ch = gh();
		return t ? -x : x;
	}

	inline void putc(char ch) { out[len++] = ch; }

	template<class T>

	inline void write(T x)
    {
		if (x < 0) putc('-'), x = -x;
		if (x > 9) write(x / 10);
		out[len++] = x % 10 + 48;
	}

	inline void flush()
    {
		fwrite(out, 1, len, stdout);
		len = 0;
	}
}
using IO::read;
using IO::write;
using IO::flush;
using IO::putc;


namespace kruskal
{
    struct Edge
    {
        int u, v, w;

        bool operator < (const Edge& rhs) const { return (w < rhs.w); }
    } edge[nd_sz];

    int tot;
    int fa[nd_sz];

    inline void add_edge(int u, int v, int w) { edge[++tot] = (Edge){u, v, w}; }

    inline int get(int x) { return (fa[x] == x ? x : fa[x] = get(fa[x])); }

    inline int kruskal()
    {
        int ans = 0;
        sort(edge + 1, edge + tot + 1);
        for (int i = 1; i <= n; i++) fa[i] = i;
        for (int i = 1; i <= tot; i++)
        {
            int fu = get(edge[i].u), fv = get(edge[i].v);
            if (fu != fv) fa[fu] = fv, ans += edge[i].w;
        }
        return ans;
    }
}

namespace tree
{
    struct Edge
    {
        int to, nxt, w;
    } edge[maxn];

    int tot = 0;
    int head[maxn], dep[maxn], dfn[maxn], lg[maxn], f[maxn][lg_sz];

    inline void add_edge(int u, int v, int w)
    {
        edge[++tot] = (Edge){v, head[u], w};
        head[u] = tot;
    }

    inline void dfs1(int u, int fa)
    {
        f[++tot][0] = u;
        dfn[u] = tot;
        // if (u == 25900) printf("%lld\n", 114514ll);
        for (int i = head[u]; i; i = edge[i].nxt)
        {
            // if (u >= 25000) printf("caonima %lld %lld\n", u, head[u]);
            int v = edge[i].to, d = edge[i].w;
            // if (u == 25900) printf("%lld -> %lld : %lld\n", u, v, d);
            if (v == fa) continue;
            dep[v] = dep[u] + d;
            // if (u == 25900) printf("%lld -> %lld : %lld\n", u, v, cnt);
            dfs1(v, u);
            f[++tot][0] = u;
        }
    }

    inline int get_min(int a, int b) { return (dep[a] < dep[b] ? a : b); }

    inline void init_st()
    {
        for (int i = 2; i <= tot; i++) lg[i] = lg[i >> 1] + 1;
        for (int j = 1; (1 << j) <= tot; j++)
            for (int i = 1; i + (1 << j) - 1 <= tot; i++)
                f[i][j] = get_min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
    }

    inline int lca(int u, int v)
    {
        u = dfn[u], v = dfn[v];
        if (u > v) swap(u, v);
        int k = lg[v - u + 1];
        return get_min(f[u][k], f[v - (1 << k) + 1][k]);
    }
}

namespace vt
{
    struct Edge
    {
        int to, nxt, w;
    } edge[maxn];

    int cnt, tot, top;
    int head[maxn], q[maxn], stk[maxn], pre[maxn], dis[maxn];
    bool vis[maxn];

    inline void clear()
    {
        for (int i = 0; i <= max(cnt, tot); i++)
        {
            q[i] = stk[i] = 0;
            head[edge[i].to] = 0;
        }
        cnt = tot = top = 0;
    }

    inline void add_edge(int u, int v, int d)
    {
        edge[++tot] = (Edge){v, head[u], d};
        head[u] = tot;
    }

    inline bool cmp(int u, int v) { return tree::dfn[u] < tree::dfn[v]; }

    inline void build()
    {
        sort(q + 1, q + cnt + 1, cmp);
        stk[1] = q[1], top = 1;
        for (int i = 2; i <= cnt; i++)
        {
            int p = tree::lca(stk[top], q[i]);
            for ( ; (top > 1) && (tree::dep[p] <= tree::dep[stk[top - 1]]); top--)
            {
                add_edge(stk[top], stk[top - 1], tree::dep[stk[top]] - tree::dep[stk[top - 1]]);
                add_edge(stk[top - 1], stk[top], tree::dep[stk[top]] - tree::dep[stk[top - 1]]);
            }
            if (top && (p != stk[top]))
            {
                add_edge(stk[top], p, tree::dep[stk[top]] - tree::dep[p]);
                add_edge(p, stk[top], tree::dep[stk[top]] - tree::dep[p]);
                stk[top] = p;
            }
            stk[++top] = q[i];
        }
        for ( ; top > 1; top--)
        {
            add_edge(stk[top], stk[top - 1], tree::dep[stk[top]] - tree::dep[stk[top - 1]]);
            add_edge(stk[top - 1], stk[top], tree::dep[stk[top]] - tree::dep[stk[top - 1]]);
        }
    }

    inline void dijkstra()
    {
        priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;
        for (int i = 1; i <= tot; i++)
        {
            int v = edge[i].to;
            if (v > n) pq.push(make_pair(0ll, v)), pre[v] = v - n, dis[v] = 0ll;
            else dis[v] = inf;
            vis[v] = false;
        }
        while (!pq.empty())
        {
            int u = pq.top().second;
            pq.pop();
            if (vis[u]) continue;
            vis[u] = true;
            for (int i = head[u]; i; i = edge[i].nxt)
            {
                int v = edge[i].to,  w = edge[i].w;
                if (dis[v] > dis[u] + w)
                {
                    dis[v] = dis[u] + w;
                    pre[v] = pre[u];
                    pq.push(make_pair(dis[v], v));
                }
            }
        }
    }

    inline void get_edge()
    {
        for (int i = 1; i <= tot; i += 2)
        {
            int u = edge[i].to, v = edge[i + 1].to;
            if (pre[u] == pre[v]) continue;
            kruskal::add_edge(pre[u], pre[v], dis[u] + dis[v] + edge[i].w);
        }
    }
}

namespace conquer
{
    struct Edge
    {
        int to, nxt, w;
    } edge[maxn];

    int rt, tot;
    int head[maxn], dep[maxn], sz[maxn], smx[maxn];
    bool vis[maxn];

    inline void add_edge(int u, int v, int d)
    {
        edge[++tot] = (Edge){v, head[u], d};
        head[u] = tot;
    }

    inline void get_dep(int u, int fa)
    {
        vt::q[++vt::cnt] = u;
        vt::add_edge(u, n + u, dep[u]);
        vt::add_edge(n + u, u, dep[u]);
        for (int i = head[u]; i; i = edge[i].nxt)
        {
            int v = edge[i].to, d = edge[i].w;
            if ((v == fa) || vis[v]) continue;
            dep[v] = dep[u] + d;
            get_dep(v, u);
        }
    }

    inline void get_rt(int u, int fa, int sum)
    {
        sz[u] = 1, smx[u] = 0;
        for (int i = head[u]; i; i = edge[i].nxt)
        {
            int v = edge[i].to;
            if ((v == fa) || vis[v]) continue;
            get_rt(v, u, sum);
            sz[u] += sz[v];
            smx[u] = max(smx[u], sz[v]);
        }
        smx[u] = max(smx[u], sum - sz[u]);
        if ((!rt) || (smx[u] < smx[rt])) rt = u;
    }

    inline void calc(int u)
    {
        vt::clear();
        dep[u] = 0;
        get_dep(u, 0);
        vt::build();
        vt::dijkstra();
        vt::get_edge();
    }

    inline void solve(int u, int sum)
    {
        calc(u);
        vis[u] = true;
        for (int i = head[u]; i; i = edge[i].nxt)
        {
            int v = edge[i].to;
            if (vis[v]) continue;
            int s = (sz[v] < sz[u] ? sz[v] : sum - sz[u]);
            rt = 0;
            get_rt(v, u, s);
            solve(rt, s);
        }
    }
}

signed main()
{
    n = read();
    // scanf("%lld", &n);
    for (int i = 1, u, v, d; i <= n - 1; i++)
    {
        u = read(), v = read(), d = read();
        // scanf("%lld%lld%lld", &u, &v, &d);
        tree::add_edge(u, v, d);
        tree::add_edge(v, u, d);
    }
    for (int i = 1, u, v, d; i <= n - 1; i++)
    {
        u = read(), v = read(), d = read();
        // scanf("%lld%lld%lld", &u, &v, &d);
        conquer::add_edge(u, v, d);
        conquer::add_edge(v, u, d);
    }
    tree::tot = 0;
    tree::dfs1(1, 0);
    tree::init_st();
    conquer::rt = 0;
    conquer::get_rt(1, 0, n);
    conquer::solve(conquer::rt, n);
    // printf("%lld\n", kruskal::kruskal());
    write(kruskal::kruskal()), putc('\n');
    flush();
    return 0;
}
posted @ 2023-01-12 15:14  kymru  阅读(46)  评论(0编辑  收藏  举报