刚刚做了这个版块的题,所以趁热打铁..(有些铁已经凉了)
LCA,就是在一棵树上找两个节点最近的公共祖先(可以理解在哪个点的时候最先相交)。
针对这类问题,我们可以用倍增的方法实现
由于以前博客写过代码,所以就不写了:自己来复习
现在开始讲例题:
这道题其实就是三个点的LCA,但是不同的是,由于有三个点,哪一个作为集合点呢?
其实可知,集合点一定在a-b,a-c,b-c这三条路径中重合的某一个点上肯定最小。
那么怎么确定是哪一个点呢?
我们可以分类讨论,三种情况:
(一)
没有说的,选点1
(二)
这种情况是选到1吗?
其实不是,是选到a才最短
(三)
那这种情况的话,a或c都可以
经验证,我们可以得出选择最深的LCA是最好的
所以就开始码代码了
思路
这也是一道神题....
我们首先考虑暴力搜索思路,那么其实可供转换的只有三种状态
1.中间的棋子往两边跳(两种)
2.边上的棋子距离中点更近的可以往中间跳
然后,这就是一颗二叉树了
1是通向儿子,2是通向父亲
那么大概思路就有了:
我们把三个棋子的位置看做一种状态
我们用类似LCA的做法来做。
首先,我们去判断是否这两种状态的根相同,就是两边的向中间的跳
这样可以直接判断输出NO
但是如果一步步跳太慢了,我们可以几步几步地跳:
画得真得丑,能看懂就行
那么第一步就实现了,现在我们已经求出了深度(步数),现在我们要把两个节点移到统一高度
一样的做法
接着,我们就开始用LCA的方法做就行了。
有些细节要扣,注意一下
由于k很小,可以在dfs的时候把k次方打出来就行了
接着,可以引进前缀和差分的思想(就像树状数组与前缀和)
可以将a到b之间的路径拆成两部分:a到它们LCA的部分和b到它们LCA的部分。再用差分思想将每个部分看成是点到根的路径去掉LCA到根的路径,这样只需维护每个点到根的路径上所有点的权值之和,就可以求出答案。
注意LCA本身的权值会被减两次,要补回来
有些oj读优才卡过,注意取模
首先,我们可以很简单地求出最小生成树
这里我们求的是严格次小,所以一定有边与最小生成树不一样,但是大部分都是一样的
应该有n-2条边(总共n-1条)都是一样的,我们去枚举每一条没被选中的边,把这条边所连接的两个点a,b的树上路径中的严格最大权值算出来,再进行替换,这样依次枚举,就可以得到答案
现在的问题是什么是严格最大权值,怎么求呢?
就是大部分情况都是找最大值,但是如果我要替换的边是我现在找到的最大权值,那么我为了满足严格次小生成树,只能用次大边来进行替换了。
那么怎么做呢?
用倍增的思想。f[x][j]表示从第x个点开始一直向上跳步这段中所得到的最大权值
而次大权值也是这样做的,所以可以用结构体
f[x][0] 就为x到fa[x]的权值,
注意替换的一些细节,一定要很仔细,庆幸自己一遍就写对了
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <vector> #include <algorithm> #include <algorithm> #include <climits> #define ll long long using namespace std; const int MAXN = 300003; int n , m; struct node{ int v ; ll w; node(){} node( int V , ll W ){ v = V; w = W; } }; struct edge{ int s , l; ll w; bool flag ; friend bool operator < ( edge a , edge b ){ return a.w < b.w; } }a[MAXN]; vector < node > G[MAXN]; int fa[MAXN]; int f[MAXN][23] ; int height[MAXN]; ll maxx[MAXN][23] , max1[MAXN][23]; ll p , k1; void read( ll &x ){ x = 0; char s = getchar(); while( s < '0' || s > '9' ){ s = getchar(); } while( s >= '0' && s <= '9' ){ x = x * 10 + s - '0'; s = getchar(); } } int findSet( int x ){ if( x != fa[x] ) fa[x] = findSet( fa[x] ); return fa[x]; } void dfs( int x , int fa1 ){ for( int i = 0 ; i < G[x].size() ; i ++ ){ int v = G[x][i].v , w = G[x][i].w; if(v == fa1 )continue; height[v] = height[x] + 1; f[v][0] = x; maxx[v][0] = w; dfs( v , x ); } } void jump( int &x , ll mu , ll &w , ll &w1 ){ for( int i = 19 ; i >= 0 ; i -- ){ if( height[f[x][i]] >= mu ){ if( w < maxx[x][i] ){ w1 = w; w = maxx[x][i]; } else if( w > maxx[x][i] ) w1 = max( w1 , maxx[x][i] ); w1 = max( w1 , max1[x][i] ); x = f[x][i]; } } } void LCA( int x , int y ){ if( height[x] > height[y] ) jump( x , height[y] , p , k1 ); else if( height[x] < height[y] ) jump( y , height[x] , p , k1 ); if( x == y ) return ; for( int i = 19 ; i >= 0 ; i -- ){ if( f[x][i] != f[y][i] ){ p = max( maxx[x][i] , p ); p = max( maxx[y][i] , p ); k1 = max( k1 , max1[x][i] ); k1 = max( k1 , max1[y][i] ); if( p > maxx[x][i] ) k1 = max( maxx[x][i] , k1 ); if( p > maxx[y][i] ) k1 = max( k1 , maxx[y][i] ); x = f[x][i] , y = f[y][i]; } } int i = 0; p = max( maxx[x][i] , p ); p = max( maxx[y][i] , p ); k1 = max( k1 , max1[x][i] ); k1 = max( k1 , max1[y][i] ); if( p > maxx[x][i] ) k1 = max( maxx[x][i] , k1 ); if( p > maxx[y][i] ) k1 = max( k1 , maxx[y][i] ); x = f[x][i] , y = f[y][i]; //printf( "%lld %lld\n" , p , k1 ); } int main(){ scanf( "%d%d" , &n , &m ); for( int i = 1; i <= m ; i ++ ) scanf( "%d%d" , &a[i].s , &a[i].l ) , read( a[i].w ); sort( a + 1 , a + m + 1 ); for( int i = 1 ; i <= n ; i ++ ) fa[i] = i; int k = 0 ; ll MST = 0; for( int i = 1 ; i <= m ; i ++ ){ int u = findSet( a[i].s ) , v = findSet( a[i].l ); if( u == v ) continue; k ++; fa[u] = v; MST += a[i].w; a[i].flag = 1; G[a[i].s].push_back(node( a[i].l , a[i].w ) ); G[a[i].l].push_back(node( a[i].s , a[i].w ) ); if( k == n - 1 ) break; } //printf( "%d" , MST ); height[1] = 1; dfs( 1 , 0 ); for( int j = 1 ; j <= 19 ; j ++ ) for( int i = 1 ; i <= n ; i ++ ){ f[i][j] = f[f[i][j-1]][j-1]; ll x = maxx[i][j-1] , y = maxx[f[i][j-1]][j-1]; maxx[i][j] = max( x , y ); max1[i][j] = max( max1[i][j-1] , max1[f[i][j-1]][j-1] ); if( x > y ) max1[i][j] = max( max1[i][j] , y ); else if( x < y ) max1[i][j] = max( max1[i][j] , x ); } ll sum = LLONG_MAX; for( int i = 1 ; i <= m ; i ++ ){ if( a[i].flag ) continue; p = 0 , k1 = 0; LCA( a[i].s , a[i].l ); if( a[i].w == p ) p = k1; if( MST + a[i].w - p < sum ) sum = MST + a[i].w - p; } printf( "%lld" , sum ); return 0; }注意数组的大小与long long
同样用前缀和做,记得把LCA的父亲减一
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <vector> #include <algorithm> #include <algorithm> #define ll long long using namespace std; const int MAXN = 300003; int n ; int f[MAXN][30]; int s[MAXN]; int sz[MAXN] , fa[MAXN]; vector< int > G[MAXN]; void dfs( int x , int fa1 ){ for( int i = 0 ; i < G[x].size() ; i ++ ){ int v = G[x][i]; if( fa1 == v ) continue; sz[v] = sz[x] + 1; fa[v] = x; f[v][0] = x; dfs( v , x ); } } void pre( ){ sz[1] = 1; dfs( 1 , 0 ); for( int j = 1 ; j <= 29 ; j ++ ) for( int i = 1 ; i <= n ; i ++ ) f[i][j] = f[f[i][j-1]][j-1]; } void jump( int &x , int mu ){ for( int i = 29 ; i >= 0 ; i -- ) if( sz[f[x][i]] >= mu ) x = f[x][i]; } int LCA( int x , int y ){ if( sz[x] > sz[y] ) jump( x , sz[y] ); else if( sz[x] < sz[y] ) jump( y , sz[x] ); if( x != y ){ for( int i = 29 ; i >= 0 ;i -- ) if( f[x][i] != f[y][i] ) x = f[x][i] , y = f[y][i]; x = f[x][0]; } return x; } int sum[MAXN]; void find_( int x , int fa1 ){ for( int i = 0 ; i < G[x].size() ; i ++ ){ int v = G[x][i]; if( v == fa1 ) continue; find_( v , x ); sum[x] += sum[v]; } } int main(){ scanf( "%d" , &n ); for( int i = 1; i <= n ; i ++ ) scanf( "%d" , &s[i] ); for( int i = 1 ; i < n ; i ++ ){ int x , y; scanf( "%d%d" , &x , &y ); G[x].push_back( y ); G[y].push_back( x ); } pre(); for( int i = 1 ; i < n ; i ++ ){ sum[s[i]] ++; sum[s[i+1]] ++; sum[ LCA( s[i] , s[i+1] ) ] -- ; sum[ f[LCA( s[i] , s[i+1] )][0] ] -= 1 ; } find_( 1 , 0 ); for( int i = 2 ; i <= n ; i ++ ) sum[s[i]] --; for( int i = 1 ; i < n ; i ++ ) printf( "%d\n" , sum[i] ); printf( "%d\n" , sum[n]) ; // printf( "%d" , sum[1] ); return 0; }考虑到是否连通,且是在线的,所以果断选择并查集
但是这道题还要输出最短使a,b联通的时间
那么可以把时间看做一条权值,用一个数组维护,做并查集的时候看哪一边深度小就放在哪一边的数组里(用秩合并):
void unionSet( int x , int y , int t ){ int u = findSet( x ) , v = findSet( y ); if( u == v ) return ; if( height[u] >= height[v] ){ fa[v] = u; sum[v] = t; if( height[u] == height[v] ) height[u] ++; } else{ sum[u] = t; fa[u] = v; } }可为什么把权值这样存呢?其实如果x与y相连,且height[x]>height[y] 那么这条边就会一直存在,也就是说fa[y] = x
那么y的父亲不会再改变了,所以就可以存在y里。
那么现在怎么找最大值呢?
直接普通的LCA就可以了,且用的是现在并查集所得到的生成树做。
至于为甚么,其实也可以分类讨论
因为儿子不变,所以权值不变