题目链接:
点击打开链接
题目大意:
给出一棵树,我们有集合,我们有操作可以加入一个点,也可以去掉一个点,每次输出当前操作后点集中的点保持连通需要的边的边权之和
题目分析:
首先对于一棵树我们在加入一个点的时候,之前的点已经是连通的,那么我们就是找到与当前要加入的点相比集合中点当中深搜序比它小的最大的点和深搜序比它大的最小的点,那么为什么是找到这两个点呢?
首先我们要找到当前加入之前点集需要加入的一条链(那个点集现在可以看做是一个联通快)。
我们记录每个点到达根的边权和 dis[]
记录了每个点的深度d[]
记录每个点fa[i][j]的第2^i个父亲
我们要知道树当中任意两点之间只有一条路径能够相互到达。
我们设要添加的点是u,深搜序小于u的最大点是x,深搜序大于u的最小点是y,首先为了保持这个点集的性质,当前u,v之间的链已经连接,所以要加的只是连接到当前这条链上的点的的那一条链的权值之和,只要连通道那个连通块当中,就可以通过其他的连通路径到达其他点,那么我们如何知道当前这种连接方法得到的结果是最小的呢?
我们能够在深搜序小于u的点当中,lca(x,u)深度最大,在深搜序大于u的点当中,lca(y,u)深度最大,因为连接到连通块的最短路径就是到达lca的这条链,所以选取这两个点的作为链的端点,一定能够得到最优解。
那么如果集合中所有的点均小于u或者所有的点均大于u,那么选取深搜序最大的和最小的即可。和上面同理,只是这样选取能够保证不管所有点都在哪个集合,lca深度最大的点都被涵盖。
那么我们知道了连的方法,那么如何知道这段具体的值,如果直接统计一定超时,所以我们选择通过先算出所有点到达根的权值之和的预处理,然后再利用公式算出要求的链的权值之和。
从根到达要添加的点u的权值之和为dis[u], lca(x,y)的深度一定不大于lca(u,x)和lca(u,y),而且一定等于其中较大的那个,所以利用公式
dis[u] - dis[lca(u,x)]-dis[lca(u,y)]+dis[lca(x,y)]就能求得最终结果。
找lca我采取的是logn的倍增法在线求取的。
dfs序一遍dfs就能得到,在线的lca需要nlogn的预处理。
就是利用fa[i][j]记录i的第2^j个父亲,因为任何一个深度都转化成一个二进制数之后,就可以利用倍增法在当前二进制位为1的情况下直接找到当前父亲,将两个点调整到同一高度,然后找到祖先,就是将一个一个向上推进的方法,通过预处理,变成了按照二进制位向上推进,直至找到父亲为止
代码如下:
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <vector> #include <set> #define MAX 100007 using namespace std; vector<int> e[MAX],c[MAX]; set<int> st; int fa[MAX][20]; int p[MAX]; int dfn[MAX]; int dis[MAX]; int cid; int d[MAX]; void dfs ( int u , int f ) { dfn[++cid] = u; p[u] = cid; for ( int i = 1 ; i < 20 ; i++ ) fa[u][i] = fa[fa[u][i-1]][i-1]; for ( int i = 0 ; i < e[u].size() ; i++ ) { int v = e[u][i]; if ( v == f ) continue; d[v] = d[u] + 1; dis[v] = dis[u] + c[u][i]; fa[v][0] = u; dfs ( v , u ); } } int lca ( int u , int v ) { if ( d[u] < d[v] ) swap ( u , v ); for ( int i = 19 ; i >= 0 ; --i ) { if ( d[fa[u][i]] >= d[v] ) u = fa[u][i]; if ( u == v ) return u; } for ( int i = 19 ; i >= 0 ; i-- ) if ( fa[u][i] != fa[v][i] ) { v = fa[v][i]; u = fa[u][i]; } return fa[u][0]; } int add ( int u ) { if ( st.empty() ) return 0; int x,y; set<int>::iterator it = st.lower_bound(p[u]) , itx = it; itx--; if ( it == st.end() || it == st.begin()) { it = st.begin(); itx = st.end(); itx--; } y = (*it); x = (*itx); y = dfn[y]; x = dfn[x]; return dis[u] - dis[lca(x,u)] - dis[lca(y,u)] + dis[lca(x,y)]; } bool mark[MAX]; int t,cc,n,q; int main ( ) { scanf ( "%d" , &t ); cc = 1; while ( t-- ) { st.clear(); cid = 0; scanf ( "%d%d" , &n , &q ); for ( int i = 0 ; i <= n ; i++ ) { mark[i] = false; e[i].clear(); c[i].clear(); } for ( int i = 1; i < n ; i++ ) { int x,y,z; scanf ( "%d%d%d" , &x , &y , &z ); e[x].push_back ( y ); e[y].push_back ( x ); c[x].push_back ( z ); c[y].push_back ( z ); } fa[1][0] = 1; d[1] = 1; dis[1] = 0; dfs ( 1 , -1 ); int sum = 0; printf ( "Case #%d:\n" , cc++ ); while ( q-- ) { int x,y; scanf ( "%d%d" , &x , &y ); int temp; if ( x == 1 ) { if ( !mark[y] ) { mark[y] = true; if ( st.size() == 0 ) { st.insert ( p[y] ); } else { temp = add(y); st.insert ( p[y] ); sum += temp; } } } else { if ( mark[y]) { mark[y] = false; st.erase ( p[y] ); if ( !st.empty()) sum -= add ( y ); } } printf ( "%d\n" , sum ); } } }