首先,我要感谢南开中学某位大佬的题解,没有那个大佬的题解,我根本不可能做出这道题,更不可能写出这篇题解,网上的题解我都看不懂,但她的我却看得懂。题在:http://oi.nks.edu.cn/zh/Problem/Details/2754,她的题解在那个下方的讨论中(%%%).
然后,这是我对这道题的理解。首先这道题我们要用到回路性质,即次小生成树一定能由最小生成树换一条边得到。想到这里,我们一定要先跑一遍最小生成树,把其边权和记录下来,把最小生成树的所有边记下来,方便建树,并把它们标记下来。这一步无论是暴力还是好方法,都要有。然后,我们再判断能不能生成次小生成树,若m<=n-1,或连最小生成树都不能生成,那么我们就输出"Keng Die!"(不能生成次小生成树)。如果可以生成次小生成树,那么暴力的时间是O(n*m*log(n)或O(n*n+m),第二zhong是先预处理出所有的u到v的路径之中的最长的一条边的长度,然而这就需要倍增和LCA了,求出pre[i][j]和maxx[i][j],其中pre[i][j]表示i的2^j级父亲,maxx[i][j]表示i到i的2^级父亲的路径中最长的一条边的长度。但是,我们可以发现,我们要枚举的只是非最小生成树边的两个端点,即把n*n优化为m-n+1,而用这条边来替代一条边,替代的肯定是两个端点在最小生成树的最短路径中最长的一条边,用LCA和倍增来求。这样一来,我们算法就变成了O(m*log(n)+(m-n+1)*log(n))即O( (2m-n)*log(n) ),这样一来,就不会超时了。
#include
#include
#include
#include
using namespace std;
struct edge{
int from,to;
int len;
}a[200005];
struct node{
int to;
int dis;
};
vector
int cmp(edge b,edge c){
return b.len
}
int n,m,sum,z;
int maxx[100005][20],pre[100005][20];
int depth[100005],vis[100005];//depth[i]表示i所在的层数,
//vis记录这条边是不是最小生成树中的一条边,其中1表示是
int p[200005],father[100005];//father表示所在子集的根
int find(int x){//并查集寻根操作
if(x!=father[x])father[x]=find(father[x]);
return father[x];
}
void unionn(int x,int y){//并查集合并操作
x=find(x);y=find(y);
father[y]=x;
}
int solve(){//求其最小生成树
sort(a+1,a+m+1,cmp);
int x,y,edges;
for(int i=1;i<=m;i++){
x=a[i].from,y=a[i].to;
if(find(x)!=find(y)){
edges++;sum+=a[i].len;
vis[i]=1;unionn(x,y);
son[a[i].from].push_back((node){a[i].to,a[i].len});
son[a[i].to].push_back((node){a[i].from,a[i].len});
//如果它是最小生成树的一条边,那么我们就把它下了,方便建树
if(edges==n-1)return 1;
}
}
return 0;
}
void buildtree(){ //建树,其中我们用数组来模拟队列(用空间换时间)。(以1为根建树)用宽搜搜出每个结点所在的层数,然后用DP(也可以说是倍增)求出now的2^i级父亲,和now到now的2^i级父亲的路径中最长的一条边的长度
int x,top=1,now; //now表示当前所在节点
p[1]=1; depth[1]=1;
while(top){
now=p[top--];
depth[now]=depth[pre[now][0]]+1;
for(int i=1;(1<//倍增(DP)预处理出now的2^i级父亲,和now到now的2^i级父亲的路径中最长的一条边的长度
pre[now][i]=pre[pre[now][i-1]][i-1];
maxx[now][i]=max(maxx[now][i-1],maxx[pre[now][i-1]][i-1]);
}
for(int i=0;i
x=son[now][i].to;
if(x==pre[now][0])continue;
pre[x][0]=now;
maxx[x][0]=son[now][i].dis;
p[++top]=x;
}
}
}
int LCA(int u,int v){ //求出u到v的路径中最长的那条边的长度
if(depth[u]
int ans=0,t=depth[u]-depth[v]; //ans表示u到v的路径中最长的那条边的长度
for(int i=0;(1< //把他们统一到同一高度
if(t&(1<
ans=max(ans,maxx[u][i]);
u=pre[u][i];
}
}
if(u==v)return ans;
for(int i=log(depth[u]);i>=0;i--){ //现在我们统一到了统一深度,就可以开始倍增往最近公共祖先靠近(它们两个之间的路径一定是从u到它们的最近公共祖先,然后再到v,这里我们是u,v同时往v靠近)
if(pre[u][i]!=pre[v][i]){
ans=max(ans,max(maxx[u][i],maxx[v][i]));
u=pre[u][i];
v=pre[v][i];
}
}
ans=max(ans,max(maxx[u][0],maxx[v][0])); //最后再到它们的最近公共祖先
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a[i].from,&a[i].to,&a[i].len);
}
for(int i=1;i<=n;i++)father[i]=i;
if(m
printf("Keng Die!");
return 0;
}
buildtree();
int ans=1111111111;
for(int i=1;i<=m;i++){
if(vis[i]==0)ans=min(ans,a[i].len-LCA(a[i].from,a[i].to));
}
printf("%d",sum+ans);
return 0;
}