• 洛谷 P3320: bzoj 3991: LOJ 2182: [SDOI2015]寻宝游戏


    题目传送门:LOJ #2182

    题意简述:

    一棵 (n) 个节点的树,边有边权。

    每个点可能是关键点,每次操作改变一个点是否是关键点。

    求所有关键点形成的极小联通子树的边权和的两倍。

    题解:

    有一个结论:DFS 序求出后,假设关键点按照 DFS 序排序后是 ({a_1,a_2,ldots ,a_k})

    那么所有关键点形成的极小联通子树的边权和的两倍等于 (mathrm{dist}(a_1,a_2)+mathrm{dist}(a_2,a_3)+cdots+mathrm{dist}(a_{k-1},a_k)+mathrm{dist}(a_k,a_1))

    画个图感性理解一下,应该是很好懂的。

    那么求一下 DFS 序,每次操作相当于往集合里加入/删除一个元素。

    假设插入 (x),它DFS序左右两边分别是 (y)(z)。那么答案加上 (mathrm{dist}(x,y)+mathrm{dist}(x,z)-mathrm{dist}(y,z)) 即可。

    删除同理。还有,求 LCA 就用个倍增或者树剖吧,Tarjan 离线比较麻烦。

    用 STL 自带的 set 容器维护起来很方便。你也可以手写树状数组/线段树/平衡树。

    #include <cstdio>
    #include <set>
    
    typedef long long LL;
    const int MN = 100005;
    
    int N, M;
    int h[MN], nxt[MN * 2], to[MN * 2], w[MN * 2], tot;
    inline void ins(int x, int y, int z) {
    	nxt[++tot] = h[x], to[tot] = y, w[tot] = z, h[x] = tot;
    }
    
    int dfn[MN], idf[MN], dfc;
    int dep[MN], faz[MN][17];
    LL dis[MN];
    
    void DFS(int u, int fz) {
    	dfn[u] = ++dfc; idf[dfc] = u; dep[u] = dep[faz[u][0] = fz] + 1;
    	for (int j = 1; 1 << j < dep[u]; ++j) faz[u][j] = faz[faz[u][j - 1]][j - 1];
    	for (int i = h[u]; i; i = nxt[i]) if (to[i] != fz) dis[to[i]] = dis[u] + w[i], DFS(to[i], u);
    }
    
    inline int lca(int x, int y) {
    	if (dep[x] < dep[y]) std::swap(x, y);
    	for (int d = dep[x] - dep[y], j = 0; d; d >>= 1, ++j)
    		if (d & 1) x = faz[x][j];
    	if (x == y) return x;
    	for (int j = 16; ~j; --j) if (faz[x][j] != faz[y][j])
    		x = faz[x][j], y = faz[y][j];
    	return faz[x][0];
    }
    
    inline LL dist(int x, int y) { return dis[x] + dis[y] - 2 * dis[lca(x, y)]; }
    
    bool vis[MN];
    std::set<int> st;
    std::set<int>::iterator it;
    LL Ans;
    
    int main() {
    	scanf("%d%d", &N, &M);
    	for (int i = 1, x, y, z; i < N; ++i) {
    		scanf("%d%d%d", &x, &y, &z);
    		ins(x, y, z), ins(y, x, z);
    	}
    	DFS(1, 0);
    	for (int m = 1, x, y, z; m <= M; ++m) {
    		scanf("%d", &x);
    		x = dfn[x];
    		if (!vis[idf[x]]) st.insert(x);
    		y = idf[(it = st.lower_bound(x)) == st.begin() ? *--st.end() : *--it];
    		z = idf[(it = st.upper_bound(x)) == st.end() ? *st.begin() : *it];
    		if (vis[idf[x]]) st.erase(x);
    		x = idf[x];
    		LL d = dist(x, y) + dist(x, z) - dist(y, z);
    		if (!vis[x]) vis[x] = 1, Ans += d;
    		else vis[x] = 0, Ans -= d;
    		printf("%lld
    ", Ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    使用C#编写SqlHelper类
    编译器perspective oo 对象模型(1) 之 初窥c++对象模型
    浅谈 编译器 & 自然语言处理
    基于c#的角色扮演游戏设计与实现
    开源的EtherCAT Master简介
    如何在Windows中编译Linux Unix的代码(采用cygwin)?
    sql拼语句例子
    IOC介绍-手写一个简单的IOC
    protocalBuffer_java版详解(转thanks)
    ProtocalBuffer_数据结构(转thanks)
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/10356940.html
Copyright © 2020-2023  润新知