图论专题训练

A

  • 题意:
    一个国家里有很多个城市,某件物品在所有城市的价格都不同,你可以在一个城市买,另一个城市卖出来获得利益,但是只能进行一次买卖。然后要从1走到n,1到n有单向,也有双向的。
  • 题解:将图分层。邻接表,spfa求出最长路(最大权值)。有三层,一层是不购买也不卖,第二层是买,一到第二层的边权为负。第三层是卖,二到第三层的边权为正。由于有向边的建立,你不能从第二/三层走回第一层图,这保证了你只做一次买卖,而不是无限做买卖。然后再设置一个最终大终点。
  • 其实这题还有很多方法 参考 https://www.luogu.org/problemnew/solution/P1073
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const ll maxn=1e5+7;
const ll inf=1<<18;
struct u
{int v,len;
};
int n,m,v[maxn],d[maxn*3+1];
vector<u> vt[maxn*3+1];//邻接表
template<class T>
void read(T &res)
{res = 0;char c = getchar();T f = 1;while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();}while(c >= '0' && c <= '9'){res = res * 10 + c - '0';c = getchar();}res *= f;
}
template<class T>
void out(T x)
{if(x < 0){putchar('-');x = -x;}if(x >= 10){out(x / 10);}putchar('0' + x % 10);
}
queue<int>Q;
bool inq[maxn*3+1];
void add(int x,int y)
{vt[x].push_back((u){y,0});vt[x+n].push_back((u){y+n,0});vt[x+2*n].push_back((u){y+2*n,0});vt[x].push_back((u){y+n,-v[x]});vt[x+n].push_back((u){y+2*n,v[x]});return;
}
void spfa()//最短路
{for(int i=1;i<=n;i++) d[i]=-inf;d[1]=0;inq[1]=true;Q.push(1);while(!Q.empty()){int tp=Q.front();Q.pop();inq[tp]=0;int len=vt[tp].size();for(int i=0;i<len;i++){u x=vt[tp][i];if(d[x.v]<d[tp]+x.len){d[x.v]=d[tp]+x.len;if(inq[x.v]==0){Q.push(x.v);inq[x.v]=1;}}}}
}
void init()
{read(n);read(m);for(int i=1;i<=n;i++) read(v[i]);for(int i=1,x,y,z;i<=m;i++){read(x);read(y);read(z);add(x,y);if(z==2) add(y,x);}vt[n].push_back((u){3*n+1,0});vt[n*3].push_back((u){n*3+1,0});//将终点和大终点连起来n=3*n+1;
}
int main()
{init();spfa();out(d[n]);printf("\n");return 0;
}

这里用到了spfa算法 Bellman-Ford 算法的队列优化 可以判断负权边。

B

  • 题意:公路是双向的,航线是单向的,但是航线的花费可能是负的。给定一个起点,求该起点到每个点的最小花费。并且注意不存在那种公路过去又从公路回来坐航线的SB情况。
  • 题解:看上去是一个比较标准的最短路,只是有负边。正解是分开来对每个连通块内做dijkstra 然后外面对于连通块用拓扑序, 细节挺多,而且我不会然后这题可以用spfa来做,朴素的spfa会T,可以用双端队列来优化。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const ll maxn=80010;
const int inf=0x3f3f3f;
int t,r,p,s;
int d[maxn];
template<class T>
void read(T &res)
{res = 0;char c = getchar();T f = 1;while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();}while(c >= '0' && c <= '9'){res = res * 10 + c - '0';c = getchar();}res *= f;
}
template<class T>
void out(T x)
{if(x < 0){putchar('-');x = -x;}if(x >= 10){out(x / 10);}putchar('0' + x % 10);
}
struct edge//建边
{int v;int cost;edge(int _v=0,int _cost=0):v(_v),cost(_cost){}
};
vector<edge>e[maxn];//邻接表
void add(int u,int v,int val)
{e[u].push_back(edge(v,val));
}
bool vis[maxn];
int cnt[maxn];
int dist[maxn];
void init()
{read(t);read(r);read(p);read(s);for(int i=1,a,b,c;i<=r;i++){read(a);read(b);read(c);add(a,b,c);add(b,a,c);}for(int i=1,a,b,c;i<=p;i++){read(a);read(b);read(c);add(a,b,c);}for(int i=1;i<=t;i++) d[i]=inf;
}
void spfa()
{vis[s]=1;d[s]=0;deque<int>q;q.push_front(s);memset(cnt,0,sizeof(cnt));cnt[s]=1;while(!q.empty()){int u=q.front();q.pop_front();vis[u]=0;for(int i=0;i<e[u].size();i++){int v=e[u][i].v;if(d[v]>d[u]+e[u][i].cost){d[v]=d[u]+e[u][i].cost;if(!vis[v]){vis[v]=true;if(!q.empty()){//SLF优化if(d[v]<d[q.front()]) q.push_front(v);else q.push_back(v);}else q.push_back(v);}}}}
}
int main()
{init();spfa();for(int i=1;i<=t;i++){if(d[i]>=inf)printf("NO PATH\n");else printf("%d\n",d[i]);}return 0;
}

SPFA算法有两个优化算法 SLF 和 LLL:
SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。
LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%.
在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。

C

  • 题意:
    n个牛做接力运动,t条道路,起点为s,终点为e,求s->e经过n条边的最短路。
  • 题解:
    我们先假设n等于2,相当于从起点到终点要经历一个断点k,这可以联想到floyd算法。先铺垫一下,(从零开始)

路径矩阵
通过一个图的权值矩阵求出它的每两点间的最短路径矩阵。
从图的带权邻接矩阵A=[a(i,j)] n×n开始,递归地进行n次更新,即由矩阵D(0)=A,按一个公式,构造出矩阵D(1);又用同样地公式由D(1)构造出D(2);……;最后又用同样的公式由D(n-1)构造出矩阵D(n)。矩阵D(n)的i行j列元素便是i号顶点到j号顶点的最短路径长度,称D(n)为图的距离矩阵,同时还可引入一个后继节点矩阵path来记录两点间的最短路径。
采用松弛技术(松弛操作),对在i和j之间的所有其他点进行一次松弛。所以时间复杂度为O(n^3);
状态转移方程
其状态转移方程如下: map[i,j]:=min{map[i,k]+map[k,j],map[i,j]};
map[i,j]表示i到j的最短距离,K是穷举i,j的断点,map[n,n]初值应该为0,或者按照题目意思来做。
当然,如果这条路没有通的话,还必须特殊处理,比如没有map[i,k]这条路。

由此可见,floyd有种矩阵的思想在里面。那当k>1时该怎么办?
参考国家队集训论文 08年的 矩阵乘法在信息学中的应用
++01邻接矩阵A的K次方C=A^K,C[i][j]表示i点到j点正好经过K条边的路径数++
对应于这道题,对邻接图进行K次floyd之后,C[i][j]就是点i到j正好经过K条边的最短路
进行k次floyd的话复杂度太大,我们可以发现,floyd算法有点像矩阵的乘法,我们可以采用矩阵快速幂来做。
这题可以用map来离散化

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n,t,s,e,num;
map<int,int>mp;
template<class T>
void read(T &res)
{res = 0;char c = getchar();T f = 1;while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();}while(c >= '0' && c <= '9'){res = res * 10 + c - '0';c = getchar();}res *= f;
}
template<class T>
void out(T x)
{if(x < 0){putchar('-');x = -x;}if(x >= 10){out(x / 10);}putchar('0' + x % 10);
}
struct matrix
{int mapp[210][210];matrix(){memset(mapp,0x3f,sizeof(mapp));}
};
matrix floyd(matrix a,matrix b)
{matrix temp;int i,j,k;for(k=1;k<=num;k++){for(i=1;i<=num;i++){for(j=1;j<=num;j++){if(temp.mapp[i][j]>a.mapp[i][k]+b.mapp[k][j]){temp.mapp[i][j]=a.mapp[i][k]+b.mapp[k][j];}}}}return temp;
}
matrix solve(matrix a,int k)
{matrix ans=a;while(k){if(k&1){ans=floyd(ans,a);}a=floyd(a,a);k>>=1;}return ans;
}
int main()
{matrix a;while(~scanf("%d%d%d%d",&n,&t,&s,&e)){num=0;mp.clear();int u,v,w;while(t--){read(w);read(u);read(v);if(mp[u]==0) mp[u]=++num;if(mp[v]==0) mp[v]=++num;if(a.mapp[mp[u]][mp[v]]>w)a.mapp[mp[u]][mp[v]]=a.mapp[mp[v]][mp[u]]=w;}a=solve(a,n-1);out(a.mapp[mp[s]][mp[e]]);}return 0;
}
  • 这里有一个矩阵快速幂的模板:
matrix floyd(matrix a,matrix b)
{matrix temp;int i,j,k;for(k=1;k<=num;k++){for(i=1;i<=num;i++){for(j=1;j<=num;j++){if(temp.mapp[i][j]>a.mapp[i][k]+b.mapp[k][j]){temp.mapp[i][j]=a.mapp[i][k]+b.mapp[k][j];}}}}return temp;
}
matrix solve(matrix a,int k)
{matrix ans=a;while(k){if(k&1){ans=floyd(ans,a);}a=floyd(a,a);k>>=1;}return ans;
}

D

  • 题意:给你几个英文字母的大小关系,1.是否能从小到大排序 2.是否有环 3.看不出来的话输出....
  • 题解:拓扑排序的模板题。in[]数组代表入度,即有多少个边以它为终点。拓扑排序就是每次以入度为0的点开始,放入队列中,并加入sorr。现在问题是如何判断有环,如何判断已排好。当有环的时候,举个例子:a<b,b<a。很明显找不到入度为0的点,所以当有环,pos要小于n。如果可以找到这个顺序,那么队列里每次都只有一个
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=120;
int n,m,in[maxn],sorr[maxn],temp[maxn];
int t,pos,num,flag,ok,stop;
char X,O,Y;
vector<int>e[maxn];
queue<int>q;
inline void init(){memset(in,0,sizeof(in));for(int i=0;i<=n;i++) e[i].clear();ok=false;flag=2;
}
int topsort(){while(!q.empty()) q.pop();for(int i=0;i<n;i++) if(in[i]==0){q.push(i);}pos=0;bool unsure=false;while(!q.empty()){if(q.size()>1) unsure=true;int u=q.front();q.pop();sorr[pos++]=u;int len=e[u].size();for(int v=0;v<len;v++){if(--in[e[u][v]]==0){q.push(e[u][v]);//入度为0放入队列中}}}if(pos<n) return 1;if(unsure) return 2;return 3;
}
int main(){int x,y;while(~scanf("%d%d",&n,&m)){if(n==0||m==0) break;init();for(int i=1;i<=m;i++){scanf(" %c%c%c%*c",&X,&O,&Y);if(ok) continue;x=X-'A',y=Y-'A';if(O=='<'){e[y].push_back(x);++in[x];//入度}else if(O=='>'){e[x].push_back(y);++in[y];}memcpy(temp,in,sizeof(in));flag=topsort();memcpy(in,temp,sizeof(temp));if(flag!=2){stop=i;ok=true;}}if(flag==3){printf("Sorted sequence determined after %d relations: ", stop);for(int i=pos-1;i>=0;i--)printf("%c",sorr[i]+'A');printf(".\n");}else if(flag==1){printf("Inconsistency found after %d relations.\n",stop);}else{printf("Sorted sequence cannot be determined.\n");}}
}

E

  • 题意:题意很妖,要使1与n通信,求最少花费。而通信公司会免费前k个最贵的,你只要付第k+1个最贵的就可以.
  • 题解:这题我们可以通过二分法,即给定一个候选距离,跑dijkstra,边权比这候选距离大,则cost为1,否则为0。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+7;
const int inf=0x3f3f3f3f;
const double eps=1e-12;
int n,p,k;
int dist[maxn];
bool vis[maxn];
template<class T>
void read(T &res)
{res = 0;char c = getchar();T f = 1;while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();}while(c >= '0' && c <= '9'){res = res * 10 + c - '0';c = getchar();}res *= f;
}
template<class T>
void out(T x)
{if(x < 0){putchar('-');x = -x;}if(x >= 10){out(x / 10);}putchar('0' + x % 10);
}
struct qnode
{int v;int c;qnode(int _v=0,int _c=0):v(_v),c(_c){}bool operator <(const qnode &r)const{return c>r.c;}
};
struct edge
{int v,cost;edge(int _v=0,int _cost=0) :v(_v),cost(_cost){}
};
vector<edge>e[maxn*2];
void add(int u,int v,int w)
{e[u].push_back(edge(v,w));
}
int dijkstra(int x,int y)
{memset(dist,inf,sizeof(dist));dist[x]=0;memset(vis,0,sizeof(vis));qnode temp;priority_queue<qnode>q;while(!q.empty()) q.pop();q.push(qnode(x,0));int cost;while(!q.empty()){temp=q.top();q.pop();int u=temp.v;if(vis[u]) continue;vis[u]=true;int len=e[u].size();for(int i=0;i<len;i++){int v=e[u][i].v;if(e[u][i].cost>=y) cost=1;//>=稍微使结果偏大一点else cost=0;if(!vis[v]&&dist[v]>dist[u]+cost){dist[v]=dist[u]+cost;q.push(qnode(v,dist[v]));}}}return dist[n];
}
int main()
{read(n);read(p);read(k);int a,b,c,maxx=0;for(int i=1;i<=p;i++){read(a);read(b);read(c);add(a,b,c);add(b,a,c);maxx=max(maxx,c);}int low=0;int high=maxx,mid,ans;while(low<=high){mid=(high+low)>>1;if(dijkstra(1,mid)<=k){//不可能出现结果的区间high=mid-1;}else{//可能出现结果的区间ans=mid;low=mid+1;}}if(low>maxx) out(-1);else out(ans);return 0;
}

这里有二分查找的模板:

while(L<=R) //二分模板{mid=(L+R)/2;if(judge(mid))//判断条件的不同{ans=mid;L=mid+1;}else{R=mid-1;}}

F

  • 题意:给一个最小生成树,添加最少花费的边,变成完全图,即每个点都连在一起。
  • 题解:我们需要高速确定点对之间是否有边以及点对之间新建边的最小权值
    可以把输入给的边来一次kruskal的过程,按权值排序,忽视之前的所有边,用并查集连接两个集合。
    用kruskal算法build最小生成树的过程中,计算最小添加边的权值(x∗y−1)∗(w+1)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=6010;
const int maxm=601000;
struct Edge
{int u,v,w;
}edge[maxm];
int tol,fa[maxn],s[maxn];
void add(int u,int v,int w)
{edge[++tol].u=u;edge[tol].v=v;edge[tol].w=w;
}
bool cmp(Edge a,Edge b)
{return a.w<b.w;
}
int findd(int x)
{if(fa[x]==-1) return x;else return fa[x]=findd(fa[x]);
}
int getfa(int x){if(fa[x]==x) return x;return fa[x]=getfa(fa[x]);
}
void init(int n)
{for(int i=0;i<=n;i++) fa[i]=i,s[i]=1;tol=0;
}
void Union(int x,int y)
{fa[x]=y;s[y]+=s[x];
}
int main()
{int t;int x,y,w,n;scanf("%d",&t);while(t--){ll ans=0;scanf("%d",&n);init(n);for(int i=1;i<n;i++){scanf("%d%d%d",&x,&y,&w);add(x,y,w);}sort(edge+1,edge+n,cmp);for(int i=1;i<n;i++){x=getfa(edge[i].u);y=getfa(edge[i].v);if(x==y) continue;ans+=1ll*(edge[i].w+1)*(s[x]*s[y]-1);Union(x,y);}printf("%lld\n",ans);}return 0;
}
  • kruskal描述:

    假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。

G

  • 题意:题目要求源点到其余点的最短路径d[i],并且求树上路径s[i]等于d[i]的生成树
  • 题解:最短路模板题。然后不只求的d[i] 还要路径还原,即d[i](1到i)有多少路径。然后用乘法原理即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e3+7;
const int maxx=5e5+10;
const int inf=0x3f3f3f3f;
const ll mod=(1<<31)-1;
int mp[maxn][maxn];
struct qnode{int v;int c;qnode(int _v=0,int _c=0):v(_v),c(_c){}bool operator <(const qnode &r) const{return c>r.c;}
};
struct edge
{int v,cost;edge(int _v=0,int _cost=0):v(_v),cost(_cost){}
};
vector<edge>e[maxx];
bool vis[maxn];
struct dist
{int v;int w;
}d[maxn];
bool cmp(dist a,dist b)
{return a.w<b.w;
}
void dijkstra(int n,int start)
{memset(vis,false,sizeof(vis));for(int i=1;i<=n;i++) d[i].w=inf,d[i].v=i;priority_queue<qnode>q;while(!q.empty()) q.pop();d[start].w=0;q.push(qnode(start,0));qnode tmp;while(!q.empty()){tmp=q.top();q.pop();int u=tmp.v;if(vis[u]) continue;vis[u]=true;for(int i=0;i<e[u].size();i++){int v=e[tmp.v][i].v;int cost=e[u][i].cost;if(!vis[v]&&d[v].w>d[u].w+cost){d[v].w=d[u].w+cost;q.push(qnode(v,d[v].w));}}}
}
inline void add(int u,int v,int w)
{e[u].push_back(edge(v,w));
}
int main(){int n,m;int u,v,w;scanf("%d%d",&n,&m);memset(mp,inf,sizeof(mp));for(int i=1;i<=m;i++){scanf("%d%d%d",&u,&v,&w);add(u,v,w);add(v,u,w);mp[u][v]=mp[v][u]=min(mp[u][v],w);}dijkstra(n,1);sort(d+1,d+1+n,cmp);ll ans=1,temp;for(int i=2;i<=n;i++){temp=0;for(int j=1;j<i;j++){if(d[i].w==mp[d[i].v][d[j].v]+d[j].w) temp++;}ans=temp*ans%mod;}printf("%lld\n",ans);return 0;
}

H

  • 题意:中文没什么好说

    H国有n个城市,这n个城市用n-l条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点。
    H国的首都爆发了一种危害性极高的传染病。当局为了控制疫清,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
    现在,在H国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军
    队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
    请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。

  • 题解

    https://www.luogu.org/problemnew/solution/P1084

RMQ预处理 然后越往上越好 就让军队往上提

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int M=50005;
inline int read(){int q=0;char ch=' ';while(ch<'0'||ch>'9')ch=getchar();while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();return q;
}
struct Edge
{int to,next;ll dis;
}e[M<<1];
struct Amey
{int rest;int id;
}ame[M],bme[M];
bool cmpmax(Amey a,Amey b){return a.rest>b.rest;}
int n,m,head[M],tol,na,nb;
int a[M],dp[M][20];
ll dis[M][20];
int vis[M],restar[M],used[M],restmin[M];
inline void add_edge(int u,int v,int val){tol++;e[tol].to=v;e[tol].dis=val;e[tol].next=head[u];head[u]=tol;
}
void dfs(int u,int fa,int val){dp[u][0]=fa;dis[u][0]=val;for(int i=head[u];i;i=e[i].next){int v=e[i].to;if(v!=fa){dfs(v,u,e[i].dis);}}
}
void rmq_init(){for(int j=1;j<=19;j++){for(int i=1;i<=n;i++){dp[i][j]=dp[dp[i][j-1]][j-1];dis[i][j]=dis[dp[i][j-1]][j-1]+dis[i][j-1];}}
}
bool checkok(int u,int fa)//检查未封住的子树
{int flag1=0,flag2=1;if(vis[u]) return 1;for(int i=head[u];i;i=e[i].next){int v=e[i].to;if(v==fa) continue;flag1=1;if(!checkok(v,u)){flag2=0;if(u==1){bme[++nb].id=v;bme[nb].rest=e[i].dis;}else return 0;}}if(!flag1) return 0;else return flag2;
}
bool judge(int mid)
{ll num;na=nb=0;ll now=1;memset(vis,0,sizeof(vis));memset(used,0,sizeof(used));//军队是否被使用memset(restar,0,sizeof(restar));//驻扎在某点 rest 最小的军队for(int i=1;i<=m;i++){//上提军队int x=a[i];num=0;for(int j=19;j>=0;j--){if(dp[x][j]>1&&num+dis[x][j]<=mid)num+=dis[x][j],x=dp[x][j];}if(dp[x][0]==1&&num+dis[x][0]<=mid){ame[++na].rest=mid-num-dis[x][0],ame[na].id=i;if(!restar[x]||ame[na].rest<restmin[x]){restmin[x]=ame[na].rest,restar[x]=i;}}else vis[x]=1;}if(checkok(1,1)) return 1;sort(ame+1,ame+na+1,cmpmax);sort(bme+1,bme+nb+1,cmpmax);used[0]=1;for(int i=1;i<=nb;i++){//贪心if(!used[restar[bme[i].id]]){used[restar[bme[i].id]]=1;continue;}while(now<=na&&(used[ame[now].id]||ame[now].rest<bme[i].rest))++now;if(now>na) return 0;used[ame[now].id]=1;}return 1;
}
int main()
{n=read();int u,v,w;ll l=0,r=500000;for(int i=1;i<n;i++){u=read();v=read();w=read();add_edge(u,v,w);add_edge(v,u,w);}dfs(1,1,0);rmq_init();m=read();for(int i=1;i<=m;i++){a[i]=read();}int ans=-1;while(l<=r){int mid=(l+r)>>1;if(judge(mid)) ans=mid,r=mid-1;else l=mid+1;}printf("%d\n",ans);return 0;
}

L

  • 题意:求严格次小生成树
  • 题解:LCA+kruscal
    先求一个最小生成树:模板题
    建树 dfs lca预处理
    然后就可以知道u到v最大的边,然后用uv的非树边换了它。 可以证明 u->v的最小生成树的距离大于等于非树边的u->v。但是要是这条边跟非树边相等,那我们就把u->v的次小边换了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll inf=0x3f3f3f3f3f3f;
const int maxn=1e5+10;
struct Edge{int u,v,w;int next;bool operator <(const Edge&c)const{return w<c.w;}
}e[maxn*8],dp[maxn*4];
int tol=0,head[maxn];
int fa[maxn];
bool vis[maxn];
void add(int u,int v,int w){tol++;e[tol].u=u;e[tol].v=v;e[tol].w=w;e[tol].next=head[u];head[u]=tol;
}
int f[maxn][21],dep[maxn];
ll maxi[maxn][19],mini[maxn][19];
void dfs(int u,int fa){f[u][0]=fa;for (int i = head[u]; i ; i=e[i].next) {int v=e[i].v;if(v==fa) continue;dep[v]=dep[u]+1;maxi[v][0]=e[i].w;mini[v][0]=-inf;dfs(v,u);}
}
int n,m;
void cal(){for (int i = 1; i <=18 ; ++i) {for (int j = 1; j <= n; ++j) {f[j][i]=f[f[j][i-1]][i-1];maxi[j][i]=max(maxi[j][i-1],maxi[f[j][i-1]][i-1]);mini[j][i]=max(mini[j][i-1],mini[f[j][i-1]][i-1]);if(maxi[j][i-1]<maxi[f[j][i-1]][i-1]&&mini[j][i]<maxi[j][i-1]){mini[j][i]=maxi[j][i-1];}else if(maxi[j][i-1]>maxi[f[j][i-1]][i-1]&&mini[j][i]<maxi[f[j][i-1]][i-1]){mini[j][i]=maxi[f[j][i-1]][i-1];}}}
}
inline int getfather(int a){if(fa[a]==a) return a;else return fa[a]=getfather(fa[a]);
}
ll sum;
inline ll qmax(int u,int v,int maxx){ll ans=-inf;for (int i = 18; i >=0; --i) {if(dep[f[u][i]]>=dep[v]){if(maxx!=maxi[u][i]){ans=max(ans,maxi[u][i]);}else if(maxx==maxi[u][i]){ans=max(ans,mini[u][i]);}}}return ans;
}
inline int lca(int u,int v){if(dep[u]<dep[v]) swap(u,v);for(int i=19;i>=0;i--){if(dep[u]>=dep[v]+(1<<i)){u=f[u][i];}}if(u==v){return u;}for(int i=19;i>=0;i--){if(f[u][i]!=f[v][i]){u=f[u][i];v=f[v][i];}}return f[u][0];
}
inline void kruscal(){sort(dp+1,dp+1+m);int temp=0;for (int i = 1; i <= m; ++i) {int a=getfather(dp[i].u),b=getfather(dp[i].v);if(a!=b){vis[i]=true;fa[a]=b;temp++;sum+=dp[i].w;add(dp[i].u,dp[i].v,dp[i].w);add(dp[i].v,dp[i].u,dp[i].w);}if(temp==n-1) break;}
}
ll ans=inf;
inline void solve(){for (int i = 1; i <=m ; ++i) {if(!vis[i]){int u=dp[i].u;int v=dp[i].v;int w=dp[i].w;int gf=lca(u,v);ll m1=qmax(u,gf,w);ll m2=qmax(v,gf,w);ans=min(ans,sum-max(m1,m2)+w);}}
}
int main() {scanf("%d%d",&n,&m);for (int i = 0; i <= n; ++i) {fa[i]=i;}for(int i=1;i<=m;i++){scanf("%d%d%d",&dp[i].u,&dp[i].v,&dp[i].w);}kruscal();mini[1][0]=-inf;dep[1]=1;dfs(1,1);cal();solve();printf("%lld\n",ans);return 0;
}

转载于:https://www.cnblogs.com/smallocean/p/9413337.html

图论专题训练 (更新中)相关推荐

  1. 【图论专题】BFS中的双向广搜 和 A-star

    双向广搜 AcWing 190. 字串变换 #include <cstring> #include <iostream> #include <algorithm> ...

  2. C++——素数(质数)专题训练4

    作者有话说:时隔一年的质数专题训练更新啦~  近期会多多更新笔记!!! 1255:求质数 时间限制: 1.000 Sec  内存限制: 128 MB 题目描述 输入正整数n,输出不大于n的最大质数 输 ...

  3. 并查集算法总结专题训练

    并查集算法总结&专题训练 1.概述 2.模板 3.例题 1.入门题: 2.与别的算法结合: 3.考思维的题: 4.二维转一维: 5.扩展域并查集&边带权并查集: 4.总结 1.概述 并 ...

  4. .[算法]图论专题之最短路径

    .[算法]图论专题之最短路径 作者:jasonkent27 转载请注明出处:www.cnblogs.com/jasonkent27 1. 前言 1.1 最短路引入 小明和小天现在住在海口(C1),他们 ...

  5. 什么是目标检测?理论+实操(github全面解析)?(持续更新中)

    温馨提示:文章内容完整但是过长,由于前后内容有关联,读者学习可以多开几个浏览器分屏有助于定位 目录 目标检测理论部分: 1.目标检测介绍 2.YOLOv5的检测原理 3.目标检测的意义 4.目标检测的 ...

  6. 【0514 更新中】CVPR2019 论文解读汇总

    CVPR2019 论文解读汇总(0514 更新中) 原文 http://bbs.cvmart.net/topics/287/cvpr2019 计算机视觉顶会CVPR 2019 接收结果已经出来啦,相关 ...

  7. 图论专题-学习笔记:虚树

    图论专题-学习笔记:虚树 1. 前言 2. 详解 2.1 虚树定义 2.2 虚树构造 2.3 例题 3. 总结 4. 参考资料 1. 前言 虚树,主要是用于一类树上问题,这类问题通常是需要取一些关键点 ...

  8. 算法学习-动态规划,纸老虎打倒他(持续更新中)

    文章目录 基础知识 线性DP 相关题目 45.跳跃游戏II 70.爬楼梯 746.使用最小花费爬楼梯 62.不同路径 63.不同路径2 343.整数拆分 96.不同的二叉搜索树 91.解码方法 119 ...

  9. 数据挖掘分析相关面试题(亲身经历),持续更新中(最新一次为20210209)

    以下大多数都是博主或者博主同事经历过的面试题哟~关于工作内容的就不写啦,一些基础面试题跟大家分享下 多看看面试题也能够让你快速了解自己的能力和短缺的地方哦~ 本篇博客会持续更新,也希望大家多多提供一些 ...

最新文章

  1. 查找局域网中的DHCP服务器
  2. 今天,我要用“数”,向你表白。
  3. 使左对角线和右对角线上的元素为0
  4. .NET Core 如何禁止.resx文件自动生成Designer.cs
  5. 层次分析法之python
  6. Apollo测试通知登记
  7. linux shell 版本信息,查看各种Linux系统版本信息的Shell命令
  8. Windows活动目录(域服务器)经典系列图文教程
  9. ESP8266获取B站粉丝数
  10. 【Love2d从青铜到王者】第九篇:Love2d之库(library)
  11. js01--js基础入门
  12. 【python】pythonPTA编程练习2
  13. Uni-app 小程序使用腾讯云IM实时通讯
  14. 编程英文单字的标准缩写
  15. 树形数据的搜索方法---javascript
  16. 网站安全监测系统软件平台分享 、网站安全监控平台有哪些
  17. java代码创建Set
  18. java中抛出异常后代码继续执行的问题
  19. Javascript屏蔽输入框的违禁词
  20. 基于51单片机的数字温度计及电压表设计【仿真设计-127】

热门文章

  1. 湖南2021计算机对口,2021湖南到底什么是对口高考?对口高考的优势?
  2. 运用python画光刻板版图-1引言
  3. 国内最大盗号软件被查:缴获703万张手机黑卡
  4. 压力测试总共需要几个步骤?
  5. LiveVideoStackCon 2018技术培训 — 从FFmpeg视频编码到抖音式视频特效实现
  6. 怀念:原SUN公司官方主页www.sun.com已转到oracle网站目录下
  7. 页面点击出现小心心。
  8. VSCode窗口全部字体大小缩放设置 - 快捷键
  9. 提高组CSP-S初赛模拟试题整理2
  10. 强势来袭!有人破了阿里云盘的限制!官方慌了!!!(附最新福利码扩容领取!)