「LuoguP4180」 【模板】严格次小生成树[BJWC2010](倍增 LCA Kruscal

题目描述

小C最近学了很多最小生成树的算法,Prim算法、Kurskal算法、消圈算法等等。正当小C洋洋得意之时,小P又来泼小C冷水了。小P说,让小C求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说:如果最小生成树选择的边集是EM,严格次小生成树选择的边集是ES,那么需要满足:(value(e)表示边e的权值) sum_{e in E_M}value(e)<sum_{e in E_S}value(e)eEMvalue(e)<eESvalue(e)

这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

输入输出格式

输入格式:

第一行包含两个整数N 和M,表示无向图的点数与边数。 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z。

输出格式:

包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)

输入输出样例

输入样例#1: 复制
5 6
1 2 1 
1 3 2 
2 4 3 
3 5 4 
3 4 3 
4 5 6 
输出样例#1: 复制
11

说明

数据中无向图无自环; 50% 的数据N≤2 000 M≤3 000; 80% 的数据N≤50 000 M≤100 000; 100% 的数据N≤100 000 M≤300 000 ,边权值非负且不超过 10^9 。

题解

话说这个还没有经典到当模板吧qwq

先做一个最小生成树,图被分成树边和非树边。

在这个树上随便找个点当根,预处理好倍增的东西(包括往上$2^j$步的祖先编号,往祖先走的这条路上最长的边,这条路上第二长的边

然后把每条非树边跑一遍,设两点为$u,v$。

可以想到,从$u$到$v$的这条树上路径中找一条最大但小于这条非树边的边替换,这样的结果是严格次小生成树的一个备选结果。

所以就一边求lca一边跟那一段的最长边和第二长边对比啊什么的。

//记录第二长边主要是防止最长边和这条非树边一样长

作为紫题非常好写,非常不容易错。(毕竟只是倍增lca和Kruscal揉到一起的一个东西,元件都不难写qwq

但确实很长就是了qwq

  1 /*
  2     qwerta 
  3     P4180 【模板】严格次小生成树[BJWC2010] Accepted 
  4     100
  5     代码 C++,2.72KB
  6     提交时间 2018-10-31 19:50:25
  7     耗时/内存 1812ms, 35376KB
  8 */
  9 #include<algorithm>
 10 #include<iostream>
 11 #include<cstring>
 12 #include<cstdio>
 13 #include<cmath>
 14 using namespace std;
 15 inline int read()
 16 {
 17     char ch=getchar();
 18     int x=0;
 19     while(!isdigit(ch))ch=getchar();
 20     while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
 21     return x;
 22 }
 23 const int MAXN=1e5+3,MAXM=3e5+3;
 24 struct emm{
 25     int x,y,l,tag;
 26 }b[MAXM];//用来存原图,tag表示是否为树边
 27 bool cmp(emm qaq,emm qwq){
 28     return qaq.l<qwq.l;
 29 }//用来最小生成树
 30 int fa[MAXN];
 31 int fifa(int x)
 32 {
 33     if(fa[x]==x)return x;
 34     return fa[x]=fifa(fa[x]);
 35 }
 36 struct ahh{
 37     int e,f,l;
 38 }a[2*MAXN];//用来存树
 39 int h[MAXN];
 40 int tot=0;
 41 void con(int x,int y,int l)//连树边
 42 {
 43     a[++tot].f=h[x];
 44     h[x]=tot;
 45     a[tot].e=y;
 46     a[tot].l=l;
 47     a[++tot].f=h[y];
 48     h[y]=tot;
 49     a[tot].e=x;
 50     a[tot].l=l;
 51     return;
 52 }
 53 int w[MAXN],d[MAXN];//w把边权记在深度较深的点上,d为深度
 54 void dfs(int x)//dfs建树
 55 {
 56     for(int i=h[x];i;i=a[i].f)
 57     if(!d[a[i].e])
 58     {
 59         fa[a[i].e]=x;
 60         d[a[i].e]=d[x]+1;
 61         w[a[i].e]=a[i].l;
 62         dfs(a[i].e);
 63     }
 64     return;
 65 }
 66 int f[MAXN][21];//往上2^j步的祖先
 67 int mac[MAXN][21];//往上2^j步途中最长的边长
 68 int macc[MAXN][21];//往上2^j步途中严格第二长的边长
 69 int zz[5];//辅助数组
 70 bool cmpp(int qaq,int qwq){
 71     return qaq>qwq;
 72 }//用来给zz排序
 73 int main()
 74 {
 75     //freopen("a.in","r",stdin);
 76     int n=read(),m=read();
 77     for(int i=1;i<=m;++i)
 78     {
 79         b[i].x=read(),b[i].y=read(),b[i].l=read();
 80     }
 81     //做个最小生成树↓
 82     sort(b+1,b+m+1,cmp);
 83     for(int i=1;i<=n;++i)
 84     fa[i]=i;
 85     int k=n-1,j=0;
 86     long long sum=0;
 87     while(k)
 88     {
 89         j++;
 90         int u=fifa(b[j].x),v=fifa(b[j].y);
 91         if(u!=v)
 92         {
 93             fa[u]=v;
 94             //cout<<b[j].x<<" "<<b[j].y<<" "<<b[j].l<<endl;
 95             b[j].tag=1;
 96             sum+=b[j].l;//记下树边权值和
 97             con(b[j].x,b[j].y,b[j].l);//把两点连起来
 98             k--;
 99         }
100     }
101     //dfs建树↓
102     memset(fa,0,sizeof(fa));
103     int s=min(n,7);
104     d[s]=1;
105     dfs(s);
106     //倍增↓
107     for(int i=1;i<=n;++i)
108     {
109         // /cout<<i<<" "<<fa[i]<<endl;
110         f[i][0]=fa[i];
111         mac[i][0]=w[i];
112     }
113     for(int j=1;j<=14;++j)
114     for(int i=1;i+(1<<j)-1<=n;++i)
115     {
116         f[i][j]=f[f[i][j-1]][j-1];
117         zz[1]=mac[i][j-1],zz[2]=macc[i][j-1];
118         zz[3]=mac[f[i][j-1]][j-1],zz[4]=macc[f[i][j-1]][j-1];
119         //这里用zz只是懒得写if
120         sort(zz+1,zz+5,cmpp);//从大到小sort一下
121         unique(zz+1,zz+5);//因为要严格第二大所以unique一下
122         mac[i][j]=zz[1];
123         macc[i][j]=zz[2];
124     }
125     long long ans=1e14+2333;
126     for(int i=1;i<=m;++i)
127     if(!b[i].tag)//循环每条非树边
128     {
129         int u=b[i].x,v=b[i].y;
130         //倍增求lca
131         if(d[u]<d[v])swap(u,v);
132         for(int j=14;j>=0;--j)
133         if(d[u]-d[v]>=(1<<j))
134         {
135             if(b[i].l>mac[u][j])//如果非树边比最长边长
136             ans=min(ans,sum-mac[u][j]+b[i].l);//这两条替换是一种可能方案
137             else if(b[i].l==mac[u][j])//否则如果非树边跟最长边一样长
138             ans=min(ans,sum-macc[u][j]+b[i].l);//把这条边跟第二长边替换是一种可能方案
139             u=f[u][j];//记得往上跳
140         }
141         if(u==v)continue;
142         for(int j=14;j>=0;--j)
143         if(f[u][j]!=f[v][j])
144         {
145             if(b[i].l>mac[u][j])
146             ans=min(ans,sum-mac[u][j]+b[i].l);
147             else if(b[i].l==mac[u][j])
148             ans=min(ans,sum-macc[u][j]+b[i].l);
149             u=f[u][j];
150             if(b[i].l>mac[v][j])
151             ans=min(ans,sum-mac[v][j]+b[i].l);
152             else if(b[i].l==mac[v][j])
153             ans=min(ans,sum-macc[v][j]+b[i].l);
154             v=f[v][j];
155         }
156         if(b[i].l>w[u])
157         ans=min(ans,sum-w[u]+b[i].l);
158         if(b[i].l>w[v])
159         ans=min(ans,sum-w[v]+b[i].l);
160     }
161     cout<<ans<<endl;//输出撒花
162     return 0;
163 }
原文地址:https://www.cnblogs.com/qwerta/p/9886338.html