#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;constint maxn =2e5+100;structnode{int u, v, w;}e[maxn];int fa[maxn];int n, m;int ans;int cnt;boolcmp(node a, node b){return a.w < b.w;}intfind(int x){if(x == fa[x])return x;return fa[x]=find(fa[x]);}voidkruskal(){sort(e +1, e +1+ m, cmp);for(int i =1; i <= m; i++){int fu =find(e[i].u);int fv =find(e[i].v);if(fu == fv)continue;
ans += e[i].w;
fa[fu]= fv;if(++cnt == n -1)break;}}intmain(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;for(int i =1; i <= n; i++)fa[i]= i;for(int i =1; i <= m; i++)cin >> e[i].u >> e[i].v >> e[i].w;kruskal();if(cnt == n -1)cout << ans << endl;else cout <<"orz"<< endl;return0;}
二分图最大匹配(匈牙利算法)
#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;int n;int a[10004];int vis[10004];int pos[10004];int edg[10004][10004];booldfs(int x){for(int j =1; j <= n; j++){if(!vis[j]&& edg[x][j]){
vis[j]=1;if(pos[j]==0||dfs(pos[j])){
pos[j]= x;return1;}}}return0;}inthungarian(){int ans =0;for(int i =1; i <= n; i++){memset(vis,0,sizeof(vis));if(dfs(i))ans++;}return ans;}intmain(){
ios::sync_with_stdio(false);
cin >> n;for(int i =1; i <= n; i++)cin >> a[i];for(int i =1; i <= n-1; i++){for(int j = i +1; j <= n; j++){
edg[a[i]][a[j]]=1;//建立边视具体题目而定,故以下操作略}}return0;}
单源最短路(dijkstra)
#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;constint maxn =100;int dis[maxn];int pre[maxn];int n, m, x, y, z, aim;//输入n和m,代表n个节点,m条边,然后是m行输入,//每行有x,y,z,代表x到y的路距离为z,aim为终点。structedge{int to;//终点int cost;//权重edge(int x,int y):to(x),cost(y){};};typedef pair<int,int>P;//first为最短距离,second为顶点编号
vector<edge> graph[maxn];//邻接表voiddijkstra(int s){//初始化memset(pre,-1,sizeof(pre));memset(dis,0x3f,sizeof(dis));//距离初始化为无穷大
dis[s]=0;//自身到自身的距离为0//默认对pair.first的大小进行排序 从小到大
priority_queue<P, vector<P>, greater<P>>que;
que.push(P(0, s));while(!que.empty()){
P p = que.top();
que.pop();//顶点编号int v = p.second;//dist[v]经过松弛操作变小,压入堆中的路径失去价值if(dis[v]< p.first)continue;//利用最短边进行松弛for(int i =0; i < graph[v].size(); i++){
edge e = graph[v][i];if(dis[e.to]> dis[v]+ e.cost){
dis[e.to]= dis[v]+ e.cost;
que.push(P(dis[e.to], e.to));
pre[e.to]= v;}}}}voidrecout(int x){if(x ==-1)return;//递归输出recout(pre[x]);
cout << x <<"->";}intmain(){
ios::sync_with_stdio(false);
cin >> n >> m;for(int i =1; i <= m; i++){
cin >> x >> y >> z;
graph[x].push_back(edge(y, z));//起点x终点y权重w}//起点为1时dijkstra(1);//终点aim
cin >> aim;recout(pre[aim]);
cout << aim << endl;//打印到aim的最短距离
cout << dis[aim]<< endl;return0;}
单源最短路(spfa)
#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;constint maxn =200010;structedg{int to, nextnum, w;//to为终点,nextnum为这条边的起点所连的下一条边的边序号,w为权重}e[maxn];int n, m;int edgnum[maxn];//当前起点的多条边的最后一条的编号int dis[maxn];bool vis[maxn];int cnt[maxn];int num;voidadd(int x,int y,int z){
e[++num].to = y;
e[num].nextnum = edgnum[x];
e[num].w = z;
edgnum[x]= num;return;}boolspfa(int s){
queue<int>q;memset(vis,0,sizeof(vis));memset(dis,0x3f,sizeof(dis));memset(cnt,0,sizeof(cnt));
q.push(s);
vis[s]=1;
dis[s]=0;
cnt[s]++;while(!q.empty()){int qf = q.front();
q.pop();
vis[qf]=0;for(int i = edgnum[qf]; i; i = e[i].nextnum){int temp;
temp = e[i].to;if(dis[temp]> dis[qf]+ e[i].w){
dis[temp]= dis[qf]+ e[i].w;if(!vis[temp]){
q.push(temp);
cnt[temp]++;
vis[temp]=1;if(cnt[temp]> n)return0;}}}}return1;}intmain(){
ios::sync_with_stdio(false);int n, m, S, T;
cin >> n >> m >> S >> T;for(int i =1; i <= m; i++){int x, y, z;
cin >> x >> y >> z;add(x, y, z);}if(!spfa(S))cout <<"有负环"<< endl;else cout << dis[T]<< endl;return0;}//tips:与Dijkstra的不同:1.可以判断有负环//2.vis数组表示是否进队列3.Spfa容易被卡,Dijkstra的堆优化比Spfa快,//图越复杂Spfa越被卡,Dijkstra更好用
问题:分层图最短路是指在可以进行分层图的图上解决最短路问题。
分层图:可以理解为有多个平行的图。
一般模型是:在一个正常的图上可以进行 k 次决策,对于每次决策,
不影响图的结构,只影响目前的状态或代价。
一般将决策前的状态和决策后的状态之间连接一条权值为决策代价的边,
表示付出该代价后就可以转换状态了。
一般有两种方法解决分层图最短路问题:
1.建图时直接建成k +1层。
2.多开一维记录机会信息。当然具体选择哪一种方法,看数据范围吧 。
法一:开4nk的数据
#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;#definepipair<int,int>#defineinff0x3f3f3f3fconstint maxn =5e5+20;struct{int to, next, w;}edg[maxn];int head[maxn];int dis[maxn];int vis[maxn];int cnt;int n, m, s, t, k;voidinit(){memset(head,-1,sizeof(head));memset(dis,0x3f,sizeof(dis));memset(vis,0,sizeof(vis));
cnt =0;}voidadd(int u,int v,int w){
edg[++cnt].to = v;
edg[cnt].w = w;
edg[cnt].next = head[u];
head[u]= cnt;}voiddijkstra(){
priority_queue<pi, vector<pi>, greater<pi>>q;
dis[s]=0;
q.push({ dis[s],s });while(!q.empty()){int now = q.top().second;
q.pop();if(vis[now])continue;
vis[now]=1;for(int i = head[now]; i; i = edg[i].next){int v = edg[i].to;if(!vis[v]&& dis[v]> dis[now]+ edg[i].w){
dis[v]= dis[now]+ edg[i].w;
q.push({ dis[v],v });}}}}intmain(){
ios::sync_with_stdio(false);while(cin >> n >> m >> s >> t >> k){init();while(m--){int u, v, w;
cin >> u >> v >> w;for(int i =0; i <= k; i++){add(u + i * n, v + i * n, w);add(v + i * n, u + i * n, w);if(i != k){add(u + i * n, v +(i +1)* n,0);add(v + i * n, u +(i +1)* n,0);}}}dijkstra();int ans = inff;for(int i =0; i <= k; i++)ans =min(ans, dis[t + i * n]);
cout << ans << endl;}return0;}
法二
我们把dis数组和vis数组多开一维记录k次机会的信息。
dis[i][j] 代表到达 i 用了 j 次免费机会的最小花费.
vis[i][j] 代表到达 i 用了 j 次免费机会的情况是否出现过.
更新的时候先更新同层之间(即花费免费机会相同)的最短路,
然后更新从该层到下一层(即再花费一次免费机会)的最短路。
不使用机会 dis[v][c]= min(min,dis[now][c]+ edge[i].w);
使用机会 dis[v][c +1]= min(dis[v][c +1],dis[now][c]);#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;#definepipair<int,int>#defineinff0x3f3f3f3fconstint maxn =1e6+20;structnode{int to, next, w;}edg[maxn];int head[maxn];int dis[maxn][20];int vis[maxn][20];int cnt;int n, m, s, t, k;voidinit(){memset(head,-1,sizeof(head));memset(dis,0x3f,sizeof(dis));memset(vis,0,sizeof(vis));
cnt =0;}voidadd(int u,int v,int w){
edg[++cnt].to = v;
edg[cnt].w = w;
edg[cnt].next = head[u];
head[u]= cnt++;}voiddijkstra(){
priority_queue<pi, vector<pi>, greater<pi>>q;
dis[s][0]=0;
q.push({0,s });while(!q.empty()){int now = q.top().second;
q.pop();int ceng = now / n;
now %= n;if(vis[now][ceng])continue;
vis[now][ceng]=1;for(int i = head[now]; i !=-1; i = edg[i].next){int v = edg[i].to;if(!vis[v][ceng]&& dis[v][ceng]> dis[now][ceng]+ edg[i].w){
dis[v][ceng]= dis[now][ceng]+ edg[i].w;
q.push({ dis[v][ceng],v + ceng * n });}}if(ceng < k){for(int i = head[now]; i !=-1; i = edg[i].next){int v = edg[i].to;if(!vis[v][ceng +1]&& dis[v][ceng +1]> dis[now][ceng]){
dis[v][ceng +1]= dis[now][ceng];
q.push({ dis[v][ceng +1],v +(ceng +1)* n });}}}}}intmain(){
ios::sync_with_stdio(false);while(cin >> n >> m >> s >> t >> k){init();while(m--){int u, v, w;
cin >> u >> v >> w;add(u, v, w);add(v, u, w);}dijkstra();int ans = inff;for(int i =0; i <= k; i++)ans =min(ans, dis[t][i]);
cout << ans << endl;}return0;}
求数的直径(dfs)
//题意: 有一棵树,n − 1 n - 1n−1条边中,两两之间都有一个边权,//可以从任意一个点出发,最多经过每一条边k次,只有第一次经过的时候//才会算上边权的贡献,问最大的贡献是多少?//首先k>=2时,可以将树遍历一遍,因此最大贡献为所有边权和//k==1时,求树的直径最大//思路:先求1到p点距离最大,再求p到其他点的最大距离//参考代码:#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;constint maxn =2e5+100;int num;int h[maxn];
ll dis[maxn];bool vis[maxn];structnode{int to, nt, w;}e[maxn *2];voidinit(){
num =0;memset(h,0,sizeof(h));}voidadd(int u,int v,int w){
e[++num].to = v;
e[num].nt = h[u];
e[num].w = w;
h[u]= num;}voiddfs(int s){
vis[s]=1;for(int i = h[s]; i; i = e[i].nt){int v = e[i].to;if(vis[v])continue;
dis[v]= dis[s]+ e[i].w;dfs(v);}}int n, k;
ll sum;intmain(){
ios::sync_with_stdio(false);
cin >> n >> k;init();for(int i =1; i <= n -1; i++){int u, v, w;
cin >> u >> v >> w;add(u, v, w);add(v, u, w);
sum += w;}if(k >=2){
cout << sum << endl;return0;}memset(vis,0,sizeof(vis));memset(dis,0,sizeof(dis));dfs(1);
ll mx =0ll, p;for(int i =1; i <= n; i++){if(dis[i]> mx){
mx = dis[i];
p = i;}}memset(vis,0,sizeof(vis));memset(dis,0,sizeof(dis));dfs(p);//mx=0; mx可以不用再初始为0,想一想,为什么for(int i =1; i <= n; i++)mx =max(mx, dis[i]);
cout << mx << endl;return0;}
求连通分支数(并查集)
#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;int t;typedefstruct{int u, v;
ll w;}S;
S s[500005];int n, m, k;boolcmp(S p, S q){return p.w < q.w;}int pre[1000005];intfind(int x){if(pre[x]== x)return x;elsereturn pre[x]=find(pre[x]);}intmain(){
ios::sync_with_stdio(false);
cin >> t;while(t--){
cin >> n >> m >> k;for(int i =1; i <= n; i++)pre[i]= i;for(int i =1; i <= m; i++)cin >> s[i].u >> s[i].v >> s[i].w;sort(s +1, s +1+ m, cmp);int cnt = n;//连通分支数
ll ans;for(int i =1; i <= m; i++){if(s[i].w != s[i -1].w){if(cnt == k){
ans = s[i -1].w;break;}}if(find(s[i].u)==find(s[i].v))continue;//同一连通分支,连通分支数不变
pre[find(s[i].u)]=find(s[i].v);
cnt--;//不同连通分支则合并,连通分支数-1}if(cnt == k)cout << ans << endl;else cout <<-1<< endl;}return0;}