关键词:LCA,最小瓶颈路,树上路径问题、次小生成树
询问任意两点间最小瓶颈路(最大边最小)
法一:
1.dp[u][v]:MST上u,v路径最小瓶颈路 O(n^2)
2.每组询问输出dp[u][v] O(q)
本题n最大可取50000,此法不可取
法二:
1.anc[u][i]:MST有根树中,u的第2^i个祖先;maxcost[u][i]:u到第2^i个祖先路径上的瓶颈(最长边) O(n*logn)
2.每组询问,查找u和v分别到它们公共祖先路径上的瓶颈的最大值 O(logn*q)
询问复杂度上升,但是可以接受。
心得:树上路径问题。先转化为有根树,再用LCA解决多组询问问题(前提是一般dp的预处理复杂度较高,无法完成,而LCA预处理复杂度较低)
应用:边权减小,询问MST变化(n很大,无法O(n^2)预处理)
变题:给定一棵树,每个顶点有一个权值,询问某两点路径上的最大权值(n<=50000,q<=100000)
1.建立有根树,边权定义为子节点的点权,只有根节点附加权值 O(n)
2.同上求anc[u][i]和maxcost[u][i] O(n*logn)
3.询问时,加一个特判,如果两点公共祖先是根节点,则需要比较ans和根节点权值的大小,取较大值即可 O(q*logn)
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<math.h>
#define ll long long
#define sf scanf
#define pf printf
#define INF 1<<29
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define maxn 50010
#define maxm 100010
const ll mol=1000000007;
using namespace std;
int n,m,q;
struct Node{
int u,v,w;
bool operator<(const Node&rhs)const{
return w<rhs.w;
}
}node[maxm<<1];
struct Edge{
int to,next,w;
}edge[maxn<<2];
int head[maxn],tot,mst;
int p[maxn];
int father[maxn],depth[maxn],cost[maxn];
int anc[maxn][30],maxcost[maxn][30];
void add(int u,int v,int w){
edge[tot].to=v,edge[tot].w=w,edge[tot].next=head[u],head[u]=tot++;
}
int Find(int x){ return (p[x]==x)?x:Find(p[x]); }
void kruscal(){
for(int i=1;i<=n;i++) p[i]=i;
sort(node+1,node+m+1);
mem(head,-1),tot=0,mst=0;
for(int i=1;i<=m;i++){
int u=node[i].u,v=node[i].v,w=node[i].w;
int x=Find(u),y=Find(v);
if(x!=y){
p[x]=y;mst+=w;
add(u,v,w),add(v,u,w);
}
}
}
void root(int u,int fa,int w){
father[u]=fa,cost[u]=w;
if(fa!=-1) depth[u]=depth[fa]+1;
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].to,w=edge[i].w;
if(v!=fa) root(v,u,w);
}
}
void preprocess(){
for(int i=1;i<=n;i++){//大括号位置打错了TAT...
anc[i][0]=father[i],maxcost[i][0]=cost[i];
for(int j=1;(1<<j)<=n;j++) anc[i][j]=-1;
}
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
if(anc[i][j-1]!=-1){//判断条件很重要!
anc[i][j]=anc[anc[i][j-1]][j-1];
maxcost[i][j]=max(maxcost[i][j-1],maxcost[anc[i][j-1]][j-1]);
}
}
int query(int u,int v){
if(depth[u]<depth[v]) swap(u,v);//交换u,v,而不是交换u,v的深度
int log,ans=-INF;//记录u深度的二进制最大位数
for(log=0;(1<<log)<=depth[u];log++); log--;
for(int i=log;i>=0;i--)//上升到同一高度
if(depth[u]-(1<<i)>=depth[v]) { ans=max(ans,maxcost[u][i]);u=anc[u][i]; }
if(u==v) return ans;//v是u的祖先---特判!!!
for(int i=log;i>=0;i--)//同步上升到公共祖先节点的子节点
if(anc[u][i]!=-1&&anc[u][i]!=anc[v][i]){
ans=max(ans,maxcost[u][i]),u=anc[u][i];
ans=max(ans,maxcost[v][i]),v=anc[v][i];
}
ans=max(ans,cost[u]),ans=max(ans,cost[v]);
return ans;
}
int main(){
//freopen("a.txt","r",stdin);
int t=0;
while(scanf("%d%d",&n,&m)!=EOF){
if(t!=0) printf("\n");//输出问题---最后一组不能输出换行,换行必须写在前面
t++;
for(int i=1;i<=m;i++) scanf("%d%d%d",&node[i].u,&node[i].v,&node[i].w);
kruscal();
depth[1]=0; root(1,-1,0);
preprocess();
scanf("%d",&q);
while(q--){
int u,v; scanf("%d%d",&u,&v);
printf("%d\n",query(u,v));
}
}
}
利用最小瓶颈路结论可以求出次小生成树
O(nlogn)预处理,O(mlogn)询问
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
#define maxm 1000000
#define maxn 110
#define rad 10000
#define INF 1<<28
#define ll long long
#define rad 10000
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
int t,n,m,mst;
struct Node{
int u,v,w;
bool operator<(const Node&rhs)const{
return w<rhs.w;
}
}node[maxn*maxn];
struct Edge{
int to,next,w;
}edge[maxn*maxn];
int head[maxn],tot,p[maxn],vis[maxn][maxn],fa[maxn],cost[maxn];
int maxc[maxn][20],maxc2[maxn][20],anc[maxn][20],depth[maxn];
void add(int u,int v,int w) { edge[tot].to=v,edge[tot].next=head[u],edge[tot].w=w,head[u]=tot++; }
int Find(int u) { return p[u]==u?u:(p[u]=Find(p[u])); }
void kruscal(){
for(int i=1;i<=n;i++) p[i]=i;
mst=0,mem(vis,0),mem(head,-1),tot=0;
for(int i=1;i<=m;i++){
int x=Find(node[i].u),y=Find(node[i].v);
if(x!=y){
p[x]=y,mst+=node[i].w;
vis[node[i].u][node[i].v]=vis[node[i].v][node[i].u]=1;
add(node[i].u,node[i].v,node[i].w),add(node[i].v,node[i].u,node[i].w);
}
}
}
void dfs(int u,int f,int w){
cost[u]=w,fa[u]=f,depth[u]=(f==-1)?0:(depth[f]+1);
for(int i=i=head[u];i!=-1;i=edge[i].next)
if(edge[i].to!=f) dfs(edge[i].to,u,edge[i].w);
}
void process(){
for(int i=1;i<=n;i++){
anc[i][0]=fa[i],maxc[i][0]=cost[i],maxc2[i][0]=cost[i];
for(int j=1;(1<<j)<n;j++) anc[i][j]=-1;
}
for(int j=1;(1<<j)<n;j++)
for(int i=1;i<=n;i++)
if(anc[i][j-1]!=-1){
int a=anc[i][j-1];
anc[i][j]=anc[a][j-1];
maxc[i][j]=max(maxc[i][j-1],maxc[a][j-1]);
}
}
int query(int u,int v){
int tmp,log,i;
if(depth[u]<depth[v]) swap(u,v);
for(log=1;(1<<log)<=depth[u];log++); log--;
int ans=-INF;
for(int i=log;i>=0;i--) if(depth[u]-depth[v]>=(1<<i)) { ans=max(ans,maxc[u][i]);u=anc[u][i]; }
if(u==v) return ans;
for(int i=log;i>=0;i--)
if(anc[u][i]!=-1&&anc[u][i]!=anc[v][i]){
ans=max(maxc[u][i],ans),u=anc[u][i];
ans=max(maxc[v][i],ans),v=anc[v][i];
}
ans=max(ans,cost[u]),ans=max(ans,cost[v]);
return ans;
}
int main(){
//freopen("a.txt","r",stdin);
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&node[i].u,&node[i].v,&node[i].w);
sort(node+1,node+m+1);
kruscal();
dfs(1,-1,0);//转化为有根树
process();//倍增法预处理x的第i个祖先及路径上的最长边和次长边
int ans=INF;
for(int i=1;i<=m;i++){
int u=node[i].u,v=node[i].v,w=node[i].w;
if(!vis[u][v]){
int tmp=mst+w-query(u,v);
if(tmp<ans) ans=tmp;
}
}
printf("%d %d\n",mst,ans);
}
}
求出最长边和严格次长边解决严格次小生成树问题
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
#define maxm 500000
#define maxn 100100
#define rad 10000
#define INF 1<<28
#define ll long long
#define rad 10000
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
int t,n,m,mst;
struct Node{
int u,v,w;
bool operator<(const Node&rhs)const{
return w<rhs.w;
}
}node[maxm<<1];
struct Edge{
int to,next,w;
}edge[maxm<<1];
int head[maxn],tot,p[maxn],fa[maxn],cost[maxn];
int maxc[maxn][20],maxc2[maxn][20],anc[maxn][20],depth[maxn];
int max1,max2;
bool vis[maxm];
void add(int u,int v,int w) { edge[tot].to=v,edge[tot].next=head[u],edge[tot].w=w,head[u]=tot++; }
int Find(int u) { return p[u]==u?u:(p[u]=Find(p[u])); }
void kruscal(){
for(int i=1;i<=n;i++) p[i]=i;
mst=0,mem(vis,0),mem(head,-1),tot=0;
for(int i=1;i<=m;i++){
int x=Find(node[i].u),y=Find(node[i].v);
if(x!=y){
p[x]=y,mst+=node[i].w,vis[i]=1;
add(node[i].u,node[i].v,node[i].w),add(node[i].v,node[i].u,node[i].w);
}
}
}
void dfs(int u,int f,int w){
cost[u]=w,fa[u]=f,depth[u]=(f==-1)?0:(depth[f]+1);
for(int i=i=head[u];i!=-1;i=edge[i].next)
if(edge[i].to!=f) dfs(edge[i].to,u,edge[i].w);
}
void process(){
for(int i=1;i<=n;i++){
anc[i][0]=fa[i],maxc[i][0]=cost[i],maxc2[i][0]=0;
for(int j=1;(1<<j)<n;j++) anc[i][j]=-1;
}
for(int j=1;(1<<j)<n;j++)
for(int i=1;i<=n;i++)
if(anc[i][j-1]!=-1){
int a=anc[i][j-1];
anc[i][j]=anc[a][j-1];
maxc[i][j]=max(maxc[i][j-1],maxc[a][j-1]);
if(maxc[i][j-1]==maxc[a][j-1]) maxc2[i][j]=max(maxc2[i][j-1],maxc2[a][j-1]);
else if(maxc[i][j-1]>maxc[a][j-1]) maxc2[i][j]=max(maxc2[i][j-1],maxc[a][j-1]);
else maxc2[i][j]=max(maxc[i][j-1],maxc2[a][j-1]);
}
}
void cal(int u,int i){
u=anc[u][i],max1=max(max1,maxc[u][i]);
if(max1==maxc[u][i]) max2=max(max2,maxc2[u][i]);
else if(max1<maxc[u][i]) max2=max(max1,maxc2[u][i]);
else max2=max(max2,maxc[u][i]);
}
void cal2(int x){
if(x>max2&&x<max1) max2=x;
if(x>max1) max2=max1,max1=x;
}
void query(int u,int v){
if(depth[u]<depth[v]) swap(depth[u],depth[v]);
int d=depth[u]-depth[v],tmp=0;
for(int i=0;i<16;i++) if(d&(1<<i)) cal(u,i);//更新路径最大和严格次大值
//for(int i=16;i>=0;i--) if((tmp+=(1<<i))<=d) cal(u);//另一种写法
if(u==v) return;
for(int i=16;i>=0;i--) if(anc[u][i]!=-1&&anc[u][i]!=anc[v][i]) cal(u,i),cal(v,i);
cal2(cost[u]),cal2(cost[v]);
}
int main(){
//freopen("a.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&node[i].u,&node[i].v,&node[i].w);
sort(node+1,node+m+1);
kruscal();
dfs(1,-1,0);//转化为有根树
process();//倍增法预处理x的第i个祖先及路径上的最长边和次长边
int ans=INF;
for(int i=1;i<=m;i++){
int u=node[i].u,v=node[i].v,w=node[i].w,tmp;
max1=0,max2=0;
if(!vis[i]){
query(u,v);//求MST上任意两点的最长边和严格次长边
if(max1==w) tmp=mst+w-max2;
else tmp=mst+w-max1;
ans=min(ans,tmp);
}
}
printf("%d\n",ans);
}