图论
经验
- g,h,dist,st,p(并查集)这些数组记得初始化,根据情况初始化
- g数组加边的时候记得min一下
- dijkstra朴素版NN,堆优化版mlogm,bellman(n*m),spfa一般m,floyd(nnn)
单源最短路的建图方式
热浪
题意:给定T个点,C条无向边,求从Ts到Tc的最短路
思路:用朴素版dijkstra就可以求出来。
代码:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int N =2520,M=2*6210;
int S,T,n,m;
int h[N],w[M],e[M],ne[M],idx;
signed int dist[N];
bool st[N];
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int dj(){
memset(dist,0x3f,sizeof dist);
dist[S]=0;
for(int k=0;k<n;k++){
int t=-1;
for(int i=1;i<=n;i++){
if(!st[i]&&(t==-1||dist[t]>dist[i]))
t=i;
}
st[t]=true;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i])
dist[j]=dist[t]+w[i];
}
}
return dist[T];
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m>>S>>T;
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
cout<<dj();
return 0;
}
信使
题意:一个n个点,m条边的无向图,求从源点1到各个点的最短路的最大值。
代码:dijkstra就可以解决。如果用g数组,初始化加边的时候记得min一下。
代码:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f//注意inf
using namespace std;
typedef pair<int, int>pii;
const int N = 110,M=400;
int h[N],e[M],ne[M],w[M],idx;
int dist[N];
bool st[N];
int ans=0;
int n,m;
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dj(){
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
dist[1]=0;
for(int k=0;k<n;k++){
int t=-1;
for(int i=1;i<=n;i++){
if(!st[i]&&(t==-1||dist[t]>dist[i]))
t=i;
}
st[t]=true;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(!st[j]&&dist[j]>dist[t]+w[i])
dist[j]=dist[t]+w[i];
}
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
dj();
for(int i=2;i<=n;i++){
ans=max(ans,dist[i]);
}
if(ans==inf) ans=-1;
cout<<ans;
return 0;
}
香甜的黄油
题意:p个点c条边的无向图,求以某个点为终点,n头牛所在牧场到终点距离之和最小。
思路:反向思维,枚举每个点为源点,然后作spfa,求到每个牛所在牧场的距离之和的最小值,即每个点最短路相加。
代码:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int N =810,M=3000;
int h[N],e[M],ne[M],w[M],idx;
int dist[N];
bool st[N];
int ans=inf;
int n,m,k;
int a[N];
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int spfa(int s){//一般复杂度为O(m),用dj会超时
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
// queue<int> q;
// q.push(s);
// dist[s]=0;
// st[s]=true;
// while(q.size()){
// int t=q.front();
// q.pop();
// st[t]=false;
// for(int i=h[t];~i;i=ne[i]){
// int j=e[i];
// if(dist[j]>dist[t]+w[i]){
// dist[j]=dist[t]+w[i];
// if(!st[j]){
// q.push(j);
// st[j]=true;
// }
// }
// }
// }
//手写循环队列,注意进出队列以及都需要判断。
//若tt=0,hh=0,初始化,hh++,tt++、因为tt始终指向最后一个元素上面位置。循环队列
//若tt=-1,hh=0初始化,hh++,++tt、因为tt始终指向最后一个元素。普通队列
int q[N];
int hh=0,tt=1;
q[0]=s;
dist[s]=0;
st[s]=true;
while(hh!=tt){
int t=q[hh++];
if(hh==N) hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
if(!st[j]){
q[tt++]=j;
if(tt==N) tt=0;
st[j]=true;
}
}
}
}
int sum=0;
for(int i=0;i<k;i++){
if(dist[a[i]]==inf) return inf;
sum+=dist[a[i]];
}
return sum;
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
memset(h,-1,sizeof h);
cin>>k>>n>>m;
for(int i=0;i<k;i++) cin>>a[i];
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
for(int i=1;i<=n;i++){
ans=min(ans,spfa(i));
}
cout<<ans;
return 0;
}
最小花费
题意:求一个数经过边权相乘传递后最大值
思路:即求1w1w2…wn最大值,所以本题是一个求最大路。注意dist和g的初始化,要初始化小的
代码:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int N = 2010;
int n,m,s,t;
double g[N][N];
bool st[N];
double dist[N];
void dj(){
dist[s]=1;//求最大注意不再是初始化为0了
for(int k=0;k<n;k++){
int t=-1;
for(int i=1;i<=n;i++){
if(!st[i]&&(t==-1||dist[t]<dist[i]))
t=i;
}
st[t]=true;
for(int i=1;i<=n;i++)
dist[i]=max(dist[i],dist[t]*g[t][i]);
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
while(m--){
int a,b;
double c;
cin>>a>>b>>c;
c=(100-c)/100;
g[b][a]=g[a][b]=max(g[a][b],c);//双向边,注意了不然会wc
}
cin>>s>>t;
dj();
printf("%.8lf",100.0/dist[t]);
return 0;
}
最优乘车
题意:一共m条路线,求点1到点n最少换乘次数
思路:m条线路,每条线路转换成权为1的传递路线,最后求最短路-1.
总结:本题运用了stringstream流进行输入。
#include<iostream>
#include<algorithm>
#include<sstream>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int N =550;
bool g[N][N];
bool st[N];
int dist[N];
int n,m;
int id[N];
// void dj(){
// memset(dist,0x3f,sizeof dist);
// dist[1]=0;
// for(int k=0;k<n;k++){
// int t=-1;
// for(int i=1;i<=n;i++){
// if(!st[i]&&(t==-1||dist[t]>dist[i]))
// t=i;
// }
// st[t]=true;
// for(int i=1;i<=n;i++)
// dist[i]=min(dist[i],dist[t]+g[t][i]);
// }
// }
void dfs(int u){
if(u==n) return;
for(int i=1;i<=n;i++){
if(g[u][i]&&(dist[i]>dist[u]+1)){
dist[i]=dist[u]+1;
dfs(i);
}
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>m>>n;
// memset(g,0x3f,sizeof g);
for(int i=1;i<=n;i++) g[i][i]=1;
string line;
getline(cin,line);
while(getline(cin,line)){
stringstream ss(line);//流的运用
int p,cnt=0;
while(ss>>p) id[cnt++]=p;//getline(ss,p,',')
for(int i=0;i<cnt;i++){
for(int j=i+1;j<cnt;j++){
g[id[i]][id[j]]=true;//这里注意,不在是i,j
}
}
}
// dj();
// if(dist[n]==inf) cout<<"NO"<<endl;
// else cout<<dist[n]-1;
memset(dist,0x3f,sizeof dist);
dist[1]=0;
dfs(1);
if(dist[n]==inf) cout<<"NO"<<endl;
else cout<<dist[n]-1;
return 0;
}
昂贵的聘礼
题意:给定n个点,每个点有等级和权值,然后每个点有x条边,给出边权和终点,求到终点1的最短路。
思路:将每个点的权值转化为一个超级源点到每个点的边权,最后就是一个从超级源点到终点1的最短路问题。
代码:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
//本题重点是构造虚拟源点
const int N = 110;
int g[N][N];
int dist[N];
bool st[N];
int level[N];
int n,m;
int dj(int l,int r){
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
dist[0]=0;
for(int k=0;k<=n;k++){
int t=-1;
for(int i=0;i<=n;i++){
if(!st[i]&&(t==-1||dist[t]>dist[i]))
t=i;
}
st[t]=true;
for(int i=0;i<=n;i++){
if(level[i]>=l&&level[i]<=r)
dist[i]=min(dist[i],dist[t]+g[t][i]);
}
}
return dist[1];
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>m>>n;
memset(g,0x3f,sizeof g);
for(int i=0;i<=n;i++) g[i][i]=0;
for(int i=1;i<=n;i++){
int p,cnt;
cin>>p>>level[i]>>cnt;
g[0][i]=p;
while(cnt--){
int t,v;
cin>>t>>v;
g[t][i]=min(g[t][i],v);
}
}
int ans=inf;
for(int i=level[1]-m;i<=level[1];i++) ans=min(ans,dj(i,i+m));
cout<<ans;
return 0;
}
单源最短路的综合应用
新年好
题意:求从源点1,经过五个点的最短路。无向图
思路:以源点1,和这五个点分别作源点求最短路并且保存下来,然后全排列求最短路
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 50010, M = 200010, INF = 0x3f3f3f3f3f3f3f3f;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int q[N], dist[6][N];
int source[6];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra(int start, int dist[])
{
memset(dist, 0x3f, N * 8);//用long long记得是八个字节
dist[start] = 0;
memset(st, 0, sizeof st);
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, start});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
}
int dfs(int u, int start, int distance)
{
if (u > 5) return distance;
int res = INF;
for (int i = 1; i <= 5; i ++ )
if (!st[i])
{
int next = source[i];
st[i] = true;
res = min(res, dfs(u + 1, i, distance + dist[start][next]));
st[i] = false;
}
return res;
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
source[0] = 1;
for(int i=1;i<=5;i++) cin>>source[i];
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
cin>>a>>b>>c;
add(a, b, c), add(b, a, c);
}
for (int i = 0; i < 6; i ++ ) dijkstra(source[i], dist[i]);
memset(st, 0, sizeof st);
cout<<dfs(1,0,0);
return 0;
}
通信线路
题意:从源点1到终点n,如果路线长度小于k,不花钱为0.如果路线长度大于k条,则求k+1大的花费最小。
思路:二分答案,最小化答案r=mid,然后花费大于x的权值为1,否则为0,变成一个双端队列最短路问题。
代码:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int N = 1100,M=2e4+30;
int n,m,k;
int dist[N];
bool st[N];
int h[N],e[M],ne[M],w[M],idx;
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool check(int x){
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
dist[1]=0;
// priority_queue<pii,vector<pii>,greater<pii> > q;
deque<pii> q;
q.push_back({0,1});
while(q.size()){
auto t=q.front();
q.pop_front();
int ver=t.se;
if(st[ver]) continue;
st[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
int d=w[i]>x;
if(dist[j]>dist[ver]+d){
dist[j]=dist[ver]+d;
if(d) q.push_back({dist[j],j});
else q.push_front({dist[j],j});
}
}
}
return dist[n]<=k;
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m>>k;
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c),add(b,a,c);
}
int l=-1,r=1e6+1;//当最短路径小于k时,经费为0.当经费为r时,表示通不过
//l+1==r时,停止二分。所以l 区间[-1,1e6],r [0,1e6+1];
while(l+1<r){
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid;
}
if(r==1e6+1) r=-1;
cout<<r;
return 0;
}
道路与航线
题意:求从源点s到所有点距离之和的最小值
思路:求有负权边的最短路,用spfa可能会被卡,可以用双端队列进行优化。最优做法是用dijkstra加拓扑序列。
步骤:
1.将所有正权无向边相连能联通的点进行缩点(联通团),然后加负权边,更新联通团的入度。
2.将联通团入度为0的团加入拓扑队列,然后队列不空出队头(是一个联通团)
3.将入度为0的联通团里的所有点加入堆中,进行堆优化dijkstra。不在同一个团的点,让其团入度减1,如果减到0,加入拓扑队列中;更新距离,如果在一个团中的点加入堆中。
代码1:拓扑序列加dijkstra
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#define x first
#define y second
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 25010, M = 150010, INF = 0x3f3f3f3f;
int n, mr, mp, S;
int id[N];
int h[N], e[M], w[M], ne[M], idx;
int dist[N], din[N];
vector<int> block[N];
int bcnt;
bool st[N];
queue<int> q;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int bid)
{
id[u] = bid, block[bid].push_back(u);
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!id[j])
dfs(j, bid);
}
}
void dijkstra(int bid)
{
priority_queue<PII, vector<PII>, greater<PII>> heap;
for (auto u : block[bid])
heap.push({dist[u], u});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y, distance = t.x;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (id[j] != id[ver] && -- din[id[j]] == 0) q.push(id[j]);
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
if (id[j] == id[ver]) heap.push({dist[j], j});
}
}
}
}
void topsort()
{
memset(dist, 0x3f, sizeof dist);
dist[S] = 0;
for (int i = 1; i <= bcnt; i ++ )
if (!din[i])
q.push(i);
while (q.size())
{
int t = q.front();
q.pop();
dijkstra(t);
}
}
signed main()
{
cin >> n >> mr >> mp >> S;
memset(h, -1, sizeof h);
while (mr -- )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
for (int i = 1; i <= n; i ++ )
if (!id[i])
{
bcnt ++ ;
dfs(i, bcnt);
}
while (mp -- )
{
int a, b, c;
cin >> a >> b >> c;
din[id[b]] ++ ;
add(a, b, c);
}
topsort();
for (int i = 1; i <= n; i ++ )
if (dist[i] > INF / 2) cout << "NO PATH" << endl;
else cout << dist[i] << endl;
return 0;
}
代码二:双端队列优化spfa(放个点就行)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
// #define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int N =25010,M=2e5;
int h[N],e[M],ne[M],w[M],idx;
int dist[N];
bool st[N];
int n,r,p,s;
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void spfa(){
deque<int> q;//pii, Segmentation Fault
memset(dist,0x3f,sizeof dist);
q.push_back(s);
dist[s]=0;
while(q.size()){
auto t=q.front();
q.pop_front();
st[t]=false;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
if(!st[j]){
if(dist[j]<dist[q.front()])
q.push_front(j);
else
q.push_back(j);
st[j]=true;
}
}
}
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>r>>p>>s;
memset(h,-1,sizeof h);
while(r--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c),add(b,a,c);
}
while(p--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
spfa();
for(int i=1;i<=n;i++){
if(dist[i]>inf/2) cout<<"NO PATH"<<endl;
else cout<<dist[i]<<endl;
}
return 0;
}
最优贸易
题意:求从点1出发到终点n,在路径中一点买,另一点卖,求赚的钱最多。权值为点权
思路:本题可以转化为枚举每个点为终点,从源点1出发到终点最小的点权,减去从源点n出发到终点最大的点权的最大值。
代码:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long//用手写的循环队列不容易爆栈,不然如果用stl容器最好不要超过1e5,long long最多存储8*1e6
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int N =100010,M=2000010 ;
int n,m;
int w[N];
int hs[N],ht[N],e[M],ne[M],idx;
int dmin[N],dmax[N];
int q[N];
bool st[N];
void add(int h[],int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void spfa(int h[],int dist[],int type){
int hh=0,tt=1;
if(type==0){
memset(dist,0x3f,sizeof dmin);
dist[1]=w[1];
q[0]=1;
}else{
memset(dist,-0x3f,sizeof dmax);//求最大时,初始化为最小
dist[n]=w[n];
q[0]=n;
}
while(hh!=tt){
auto t=q[hh++];
if(hh==N) hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if((type==0&&dist[j]>min(dist[t],w[j]) )|| (type==1&&dist[j]<max(dist[t],w[j]) ) ){
if(type==0) dist[j]=min(dist[t],w[j]);
else dist[j]=max(dist[t],w[j]);
if(!st[j]){
q[tt++]=j;
if(tt==N) tt=0;
st[j]=true;
}
}
}
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
memset(hs,-1,sizeof hs);
memset(ht,-1,sizeof ht);
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(hs,a,b),add(ht,b,a);
if(c==2) add(hs,b,a),add(ht,a,b);
}
spfa(hs,dmin,0);
spfa(ht,dmax,1);
int res=0;
for(int i=1;i<=n;i++) res=max(res,dmax[i]-dmin[i]);
cout<<res;
return 0;
}
单源最短路的扩展应用
选择最佳路线
题意:对于n个点,m条边的有向图,求多个源点到终点s的最短距离。
思路:可以建立一个超级源点,超级源点到题目源点的权值设为0;
代码:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
#include<vector>
#include<queue>
#include<deque>
#include<set>
#include<map>
#define inf 0x3f3f3f3f3f3f3f3f
#define x first
#define y second
#define int long long
using namespace std;
const int N=1010,M=40000;
int dist[N];
int st[N];
int g[N][N];
int n,m,s;
int dj(){
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
dist[0]=0;
for(int k=0;k<=n;k++){
int t=-1;
for(int i=0;i<=n;i++)
if(!st[i]&&(t==-1||dist[i]<dist[t]))
t=i;
st[t]=true;
for(int i=0;i<=n;i++)
dist[i]=min(dist[i],dist[t]+g[t][i]);
}
return dist[s];
}
signed main(){
while(cin>>n>>m>>s){
memset(g,0x3f,sizeof g);
for(int i=0;i<=n;i++) g[i][i]=0;
while(m--){
int p,q,t;
cin>>p>>q>>t;
g[p][q]=min(g[p][q],t);
}
int t,o;
cin>>t;
while(t--){
cin>>o;
g[0][o]=0;
}
t=dj();
if(t==inf) cout<<-1<<endl;
else cout<<dist[s]<<endl;
}
return 0;
}
拯救大兵瑞恩
题意:求从源点(1,1)到终点(n,m)的最短距离,图上有门和钥匙
思路:dp加最短路
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
#include <set>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 11, M = 360, P = 1 << 10;
int n, m, k, p;
int h[N * N], e[M], w[M], ne[M], idx;
int g[N][N], key[N * N];
int dist[N * N][P];//dist[x][y]表示到点x状态为y的距离。
bool st[N * N][P];
set<PII> edges;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void build()
{
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
for (int u = 0; u < 4; u ++ )
{
int x = i + dx[u], y = j + dy[u];
if (!x || x > n || !y || y > m) continue;
int a = g[i][j], b = g[x][y];
if (!edges.count({a, b})) add(a, b, 0);
}
}
int bfs()
{
memset(dist, 0x3f, sizeof dist);
dist[1][0] = 0;
deque<PII> q;
q.push_back({1, 0});
while (q.size())
{
PII t = q.front();
q.pop_front();
if (st[t.x][t.y]) continue;
st[t.x][t.y] = true;
if (t.x == n * m) return dist[t.x][t.y];
if (key[t.x])
{
int state = t.y | key[t.x];
if (dist[t.x][state] > dist[t.x][t.y])
{
dist[t.x][state] = dist[t.x][t.y];
q.push_front({t.x, state});
}
}
for (int i = h[t.x]; ~i; i = ne[i])
{
int j = e[i];
if (w[i] && !(t.y >> w[i] - 1 & 1)) continue; // 有门并且没有钥匙
if (dist[j][t.y] > dist[t.x][t.y] + 1)
{
dist[j][t.y] = dist[t.x][t.y] + 1;
q.push_back({j, t.y});
}
}
}
return -1;
}
int main()
{
cin >> n >> m >> p >> k;
for (int i = 1, t = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
g[i][j] = t ++ ;
memset(h, -1, sizeof h);
while (k -- )
{
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
int a = g[x1][y1], b = g[x2][y2];
edges.insert({a, b}), edges.insert({b, a});
if (c) add(a, b, c), add(b, a, c);
}
build();
int s;
cin >> s;
while (s -- )
{
int x, y, c;
cin >> x >> y >> c;
key[g[x][y]] |= 1 << c - 1;
}
cout << bfs() << endl;
return 0;
}
最短路计数
题意:求从源点1到各点最短路的条数
思路:开一个数量数组cnt;
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
#define inf 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
using namespace std;
const int N=1e5+10,M=4e5,mod=100003;
int h[N],e[M],ne[M],w[M],idx;
int dist[N],cnt[N];
bool st[N];
int n,m;
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dj(){
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
dist[1]=0;
cnt[1]=1;
priority_queue<pii,vector<pii>,greater<pii> > heap;
heap.push({0,1});
while(heap.size()){
auto t=heap.top();
heap.pop();//不弹出队列会TLE
int ver=t.second;
if(st[ver]) continue;
st[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
if(dist[j]>dist[ver]+w[i]){
dist[j]=dist[ver]+w[i];
cnt[j]=cnt[ver];
heap.push({dist[j],j});
}else if(dist[j]==dist[ver]+w[i]){
cnt[j]=(cnt[j]+cnt[ver])%mod;
}
}
}
}
signed main(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b;
cin>>a>>b;
add(a,b,1);
add(b,a,1);
}
dj();
for(int i=1;i<=n;i++)
cout<<cnt[i]<<endl;
return 0;
}
观光
题意:求最短路和次短路只比最短路多一个单位长度的所有路线数量
思路:dist和cnt开两层分别记录最短路和次短路,dist[j][0]表示到点j的最短路,dist[j][1]表示到点j的次短路
步骤:
- 如果当前更新的距离distance+w[i]比最短路小,先更新次短路的dist,cnt,然后压入堆中,再更新最短路的dist,cnt,并且一样压入堆中。
- 如果当前更新距离等于最短路,则更新最短路的cnt即可。
- 如果当前更新距离小于次短路,则更新次短路的dist,cnt,并且将其压入堆中。
- 如果当前更新距离等于次短路,则更新次短路的cnt即可。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1010, M = 20010;
struct Ver
{
int id, type, dist;
bool operator> (const Ver &W) const
{
return dist > W.dist;
}
};
int n, m, S, T;
int h[N], e[M], w[M], ne[M], idx;
int dist[N][2], cnt[N][2];
bool st[N][2];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int dijkstra()
{
memset(st, 0, sizeof st);
memset(dist, 0x3f, sizeof dist);
memset(cnt, 0, sizeof cnt);
dist[S][0] = 0, cnt[S][0] = 1;
priority_queue<Ver, vector<Ver>, greater<Ver>> heap;
heap.push({S, 0, 0});
while (heap.size())
{
Ver t = heap.top();
heap.pop();
int ver = t.id, type = t.type, distance = t.dist, count = cnt[ver][type];
if (st[ver][type]) continue;
st[ver][type] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j][0] > distance + w[i])
{
dist[j][1] = dist[j][0], cnt[j][1] = cnt[j][0];
heap.push({j, 1, dist[j][1]});
dist[j][0] = distance + w[i], cnt[j][0] = count;
heap.push({j, 0, dist[j][0]});
}
else if (dist[j][0] == distance + w[i]) cnt[j][0] += count;
else if (dist[j][1] > distance + w[i])
{
dist[j][1] = distance + w[i], cnt[j][1] = count;
heap.push({j, 1, dist[j][1]});
}
else if (dist[j][1] == distance + w[i]) cnt[j][1] += count;
}
}
int res = cnt[T][0];
if (dist[T][0] + 1 == dist[T][1]) res += cnt[T][1];
return res;
}
int main()
{
int cases;
scanf("%d", &cases);
while (cases -- )
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
idx = 0;
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
scanf("%d%d", &S, &T);
printf("%d\n", dijkstra());
}
return 0;
}
Floyd算法
牛的旅行
题意:求连接两个连通块后的直径最小。
思路:直径最小可能是其中一个连通块的最大直径,也可能是两个连通块最大直径加上连接的边的直径大小。
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef pair<double, double> PDD;
const int N = 155;
const double INF = 1e20;
int n;
PDD q[N];
double d[N][N];
double maxd[N];
char g[N][N];
double get_dist(PDD a, PDD b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;
for (int i = 0; i < n; i ++ ) cin >> g[i];
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
if (i == j) d[i][j] = 0;
else if (g[i][j] == '1') d[i][j] = get_dist(q[i], q[j]);
else d[i][j] = INF;
for (int k = 0; k < n; k ++ )
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
double r1 = 0;
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < n; j ++ )
if (d[i][j] < INF / 2)
maxd[i] = max(maxd[i], d[i][j]);
r1 = max(r1, maxd[i]);
}
double r2 = INF;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
if (d[i][j] > INF / 2)
r2 = min(r2, maxd[i] + maxd[j] + get_dist(q[i], q[j]));
printf("%.6lf\n", max(r1, r2));
return 0;
}
排序
题意:求传递闭包,求关系。
思路:不确定时做一遍floyd算法。d=g
怎么排序?
每次找到没有被标记并且没有小于其的字母。
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 26;
int n, m;
bool g[N][N], d[N][N];
bool st[N];
void floyd()
{
memcpy(d, g, sizeof d);
for (int k = 0; k < n; k ++ )
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
d[i][j] |= d[i][k] && d[k][j];
}
int check()
{
for (int i = 0; i < n; i ++ )
if (d[i][i])//自己有大小关系矛盾
return 2;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < i; j ++ )
if (!d[i][j] && !d[j][i])//i,j关系不确定
return 0;
return 1;//所有关系都确定了
}
char get_min()
{
for (int i = 0; i < n; i ++ )
if (!st[i])
{
bool flag = true;
for (int j = 0; j < n; j ++ )
if (!st[j] && d[j][i])
{
flag = false;
break;
}
if (flag)
{
st[i] = true;
return 'A' + i;
}
}
}
int main()
{
while (cin >> n >> m, n || m)
{
memset(g, 0, sizeof g);
int type = 0, t;
for (int i = 1; i <= m; i ++ )
{
char str[5];
cin >> str;
int a = str[0] - 'A', b = str[2] - 'A';
if (!type)
{
g[a][b] = 1;
floyd();
type = check();
if (type) t = i;
}
}
if (!type) puts("Sorted sequence cannot be determined.");//不确定
else if (type == 2) printf("Inconsistency found after %d relations.\n", t);//矛盾
else
{
memset(st, 0, sizeof st);
printf("Sorted sequence determined after %d relations: ", t);//确定关系
for (int i = 0; i < n; i ++ ) printf("%c", get_min());
printf(".\n");
}
}
return 0;
}
观光之旅
题意:求至少包含三个点的最小环路径
思路:Floyd的算法核心,f[k][i][j]表示从i到j中间经过路径点为1~k。
f[k][i][j]表示经过最大点为k,经过i到j的环
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int d[N][N], g[N][N];
int pos[N][N];
int path[N], cnt;
void get_path(int i, int j)
{
if (pos[i][j] == 0) return;
int k = pos[i][j];
get_path(i, k);
path[cnt ++ ] = k;
get_path(k, j);
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= n; i ++ ) g[i][i] = 0;
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = min(g[a][b], c);
}
int res = INF;
memcpy(d, g, sizeof d);//没有一条路的时候,到自己不能初始化
for (int k = 1; k <= n; k ++ )
{
for (int i = 1; i < k; i ++ )//当前最大经过点为k-1
for (int j = i + 1; j < k; j ++ )
if ((long long)d[i][j] + g[j][k] + g[k][i] < res)
{
res = d[i][j] + g[j][k] + g[k][i];
cnt = 0;
path[cnt ++ ] = k;
path[cnt ++ ] = i;
get_path(i, j);
path[cnt ++ ] = j;
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (d[i][j] > d[i][k] + d[k][j])
{
d[i][j] = d[i][k] + d[k][j];
pos[i][j] = k;
}
}
if (res == INF) puts("No solution.");
else
{
for (int i = 0; i < cnt; i ++ ) cout << path[i] << ' ';
cout << endl;
}
return 0;
}
牛站
这里是引用
题意:求恰好经过n条边的最短路
思路:d[k][i][j]表示从i到j恰好经过k条边的最短路经。
d[a+b][i][j]=d[a][i][k]+d[b][k][j],k(1~n)
每条边可以相互独立,满足结合律,可以用快速幂,res表示由0,1,2,4,8,…边组成的距离图。
由于点太多了,可以用map来离散化点。
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
const int N = 210;
int k, n, m, S, E;
int g[N][N];
int res[N][N];
void mul(int c[][N], int a[][N], int b[][N])
{
static int temp[N][N];
memset(temp, 0x3f, sizeof temp);
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
temp[i][j] = min(temp[i][j], a[i][k] + b[k][j]);
memcpy(c, temp, sizeof temp);
}
void qmi()
{
memset(res, 0x3f, sizeof res);
for (int i = 1; i <= n; i ++ ) res[i][i] = 0;
while (k)
{
if (k & 1) mul(res, res, g); // res = res * g
mul(g, g, g); // g = g * g
k >>= 1;
}
}
int main()
{
cin >> k >> m >> S >> E;
memset(g, 0x3f, sizeof g);
map<int, int> ids;
if (!ids.count(S)) ids[S] = ++ n;
if (!ids.count(E)) ids[E] = ++ n;
S = ids[S], E = ids[E];
while (m -- )
{
int a, b, c;
cin >> c >> a >> b;
if (!ids.count(a)) ids[a] = ++ n;
if (!ids.count(b)) ids[b] = ++ n;
a = ids[a], b = ids[b];
g[a][b] = g[b][a] = min(g[a][b], c);
}
qmi();
cout << res[S][E] << endl;
return 0;
}
最小生成树
最短路网络
题意:使得各点联通且边权值最小
思路:最小生成树的模板题。用prim和kruskal都行
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n;
int w[N][N];
int dist[N];
bool st[N];
int prim()
{
int res = 0;
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
res += dist[t];
st[t] = true;
for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], w[t][j]);
}
return res;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
cin >> w[i][j];
cout << prim() << endl;
return 0;
}
局域网
题意:求总路线权值减去最小生成树的权值,即求无用边最大权值
思路:普通的最小生成树
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, M = 210;
int n, m;
struct Edge
{
int a, b, w;
bool operator< (const Edge &t)const
{
return w < t.w;
}
}e[M];
int p[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) p[i] = i;
for (int i = 0; i < m; i ++ )
{
int a, b, w;
cin >> a >> b >> w;
e[i] = {a, b, w};
}
sort(e, e + m);
int res = 0;
for (int i = 0; i < m; i ++ )
{
int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
if (a != b) p[a] = b;
else res += w;//已经联通可以把这条边去掉,也就是无用边
}
cout << res << endl;
return 0;
}
繁忙的都市
题意:求最小生成树的一段权值最大的数
思路:用kruskal算法就是形成最小生成树的最后一条边的大小
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 310, M = 10010;
int n, m;
struct Edge
{
int a, b, w;
bool operator< (const Edge &t) const
{
return w < t.w;
}
}e[M];
int p[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) p[i] = i;
for (int i = 0; i < m; i ++ )
{
int a, b, w;
cin >> a >> b >> w;
e[i] = {a, b, w};
}
sort(e, e + m);
int res = 0;
for (int i = 0; i < m; i ++ )
{
int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
if (a != b)
{
p[a] = b;
res = w;
}
}
cout << n - 1 << ' ' << res << endl;
return 0;
}
联络员
题意:在已有一些边的条件下,求最小生成树的权值
思路:已有边将其联通,然后用kruskal算法就最小生成树权。
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2010, M = 10010;
int n, m;
struct Edge
{
int a, b, w;
bool operator< (const Edge &t) const
{
return w < t.w;
}
}e[M];
int p[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) p[i] = i;
int res = 0, k = 0;
for (int i = 0; i < m; i ++ )
{
int t, a, b, w;
cin >> t >> a >> b >> w;
if (t == 1)
{
res += w;
p[find(a)] = find(b);
}
else e[k ++ ] = {a, b, w};
}
sort(e, e + k);
for (int i = 0; i < k; i ++ )
{
int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
if (a != b)
{
p[a] = b;
res += w;
}
}
cout << res << endl;
return 0;
}
连接格点
题意:求二维网格每个点的最小生成树的权值
思路:将二维网格的点转化为一维点,然后进行最小生成树算法,先添加纵向边,然后添加横向边。
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010, M = N * N, K = 2 * N * N;
int n, m, k;
int ids[N][N];
struct Edge
{
int a, b, w;
}e[K];
int p[M];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void get_edges()
{
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}, dw[4] = {1, 2, 1, 2};
for (int z = 0; z < 2; z ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
for (int u = 0; u < 4; u ++ )
if (u % 2 == z)
{
int x = i + dx[u], y = j + dy[u], w = dw[u];
if (x && x <= n && y && y <= m)
{
int a = ids[i][j], b = ids[x][y];
if (a < b) e[k ++ ] = {a, b, w};
}
}
}
int main()
{
cin >> n >> m;
for (int i = 1, t = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++, t ++ )
ids[i][j] = t;
for (int i = 1; i <= n * m; i ++ ) p[i] = i;
int x1, y1, x2, y2;
while (cin >> x1 >> y1 >> x2 >> y2)
{
int a = ids[x1][y1], b = ids[x2][y2];
p[find(a)] = find(b);
}
get_edges();
int res = 0;
for (int i = 0; i < k; i ++ )
{
int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
if (a != b)
{
p[a] = b;
res += w;
}
}
cout << res << endl;
return 0;
}
最小生成树的扩展应用
新的开始
题意:每个点都有权值,然后每个点之间边也有权值,求将所有点联通所需的最小费用
思路:给每个点建立一个超级源点,然后求包括超级源点在内的最小生成树的权值。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 310;
int n;
int w[N][N];
int dist[N];
bool st[N];
int prim()
{
memset(dist, 0x3f, sizeof dist);
dist[0] = 0;
int res = 0;
for (int i = 0; i < n + 1; i ++ )
{
int t = -1;
for (int j = 0; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true;
res += dist[t];
for (int j = 0; j <= n; j ++ ) dist[j] = min(dist[j], w[t][j]);
}
return res;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &w[0][i]);
w[i][0] = w[0][i];
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
scanf("%d", &w[i][j]);
printf("%d\n", prim());
return 0;
}
北极通讯网络
题意:有k个卫星可以将卫星的村子直接连接起来联通,然后求最小的d值,边权小于等于d的边可以使用,使得所有点相互联通
思路:最小化二分答案加最小生成树。
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 510, M = N * N / 2;
int n, k, m;
struct Edge
{
int a, b;
double w;
bool operator< (const Edge &t) const
{
return w < t.w;
}
}e[M];
PII q[M];
int p[N];
double get_dist(PII a, PII b)
{
int dx = a.x - b.x;
int dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> k;
for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < i; j ++ )
e[m ++ ] = {i, j, get_dist(q[i], q[j])};
sort(e, e + m);
for (int i = 0; i < n; i ++ ) p[i] = i;
int cnt = n;//如果有四个点,两个卫星的话,就只需要联通两条边即可。
double res = 0;
for (int i = 0; i < m; i ++ )
{
if (cnt <= k) break;
int a = find(e[i].a), b = find(e[i].b);
double w = e[i].w;
if (a != b)
{
p[a] = b;
cnt -- ;
res = w;
}
}
printf("%.2lf\n", res);
return 0;
}
走廊泼水节
题意:求将一颗最小生成树变成一个完全图的权值最小。
思路:(将两个联通部分的所有点相乘-1)*(w-1)
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 6010;
int n;
struct Edge
{
int a, b, w;
bool operator< (const Edge &t) const
{
return w < t.w;
}
}e[N];
int p[N], size[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
int T;
cin >> T;
while (T -- )
{
cin >> n;
for (int i = 0; i < n - 1; i ++ )
{
int a, b, w;
cin >> a >> b >> w;
e[i] = {a, b, w};
}
sort(e, e + n - 1);
for (int i = 1; i <= n; i ++ ) p[i] = i, size[i] = 1;
int res = 0;
for (int i = 0; i < n - 1; i ++ )
{
int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
if (a != b)
{
res += (size[a] * size[b] - 1) * (w + 1);
size[b] += size[a];
p[a] = b;
}
}
cout << res << endl;
}
return 0;
}
秘密的牛奶运输
题意:求次小生成树的权值
思路:
b站老师那里听懂的,用最近公共祖先倍增的方法。
链接
代码1:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510,M = 1e4 + 10;
struct node{ // 结构体,记录每一个点
int a,b,w;
bool f = false; // 标记是否为树边(true为树边,false为非树边)
}edge[M];
int n,m;
int p[N]; // 并查集,父节点
int dist1[N][N],dist2[N][N]; // 分别为最大距离和严格次大距离
int h[N],e[N * 2],w[N * 2],ne[N * 2],idx; // 邻接表存最小生成树
void add(int a,int b,int c){ // 建边
e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx ++;
}
bool cmp(node a,node b){ // sort
return a.w < b.w;
}
int find(int x){ // 并查集找祖宗节点
if(p[x] != x) return p[x] = find(p[x]);
return p[x];
}
void dfs(int u,int fa,int maxd1,int maxd2,int d1[],int d2[]){ // dfs预处理最大距离和严格次大距离
d1[u] = maxd1;
d2[u] = maxd2; // 记录下最大距离和严格次大距离
for(int i = h[u];~i;i = ne[i]){ // 遍历与u节点连接的所有边
int j = e[i]; // 当前点的编号
if(j != fa){ // 防止遍历到遍历过的点
int td1 = maxd1,td2 = maxd2; // 找这条边的最大距离和严格次大距离
if(w[i] > td1) td2 = td1,td1 = w[i]; // 如果这条边权比原来的最大距离更大,那么更新最大距离和严格次大距离
else if(w[i] < td1 && w[i] > td2) td2 = w[i]; // 如果这条边权比原来的最大距离小,但比严格次大距离大,那么更新严格次大距离
dfs(j,u,td1,td2,d1,d2); // 继续往下搜
}
}
}
int main(){
memset(h,-1,sizeof h); // 邻接表初始化
scanf("%d%d",&n,&m); // 读入点数和边数
for(int i = 1;i <= m;i ++) scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].w); // 读入每条边
for(int i = 1;i <= n;i ++) p[i] = i; // 并查集初始化
sort(edge + 1,edge + m + 1,cmp); // 把边按照从小到大排序
long long sum = 0; // 最小生成树
for(int i = 1;i <= m;i ++){ // 求最小生成树
int a = edge[i].a,b = edge[i].b,w = edge[i].w;
int fa = find(a),fb = find(b);
if(fa != fb){
p[fa] = fb;
sum += w;
edge[i].f = true; // 标记为树边
add(a,b,w);
add(b,a,w); // 用邻接表建树
}
}
for(int i = 1;i <= n;i ++) dfs(i,-1,-1e9,-1e9,dist1[i],dist2[i]); // 预处理最大距离和严格次大距离,点i到每个点之间一段路线的最大距离和次大距离
long long res = 1e18; // 严格次小距离
for(int i = 1;i <= m;i ++){ // 枚举每条边
if(!edge[i].f){ // 确定是非树边
long long t;
int a = edge[i].a,b = edge[i].b,w = edge[i].w;
if(w > dist1[a][b]) t = sum + w - dist1[a][b]; // 如果这条边比原来的最大距离大,那么尝试替换这条边
else if(w > dist2[a][b]) t = sum + w - dist2[a][b]; // 如果这条边比原来的严格次大距离大,那么尝试替换这条边
res = min(res,t); // 取最小的t
}
}
printf("%lld\n",res); // 输出答案
return 0;
}
代码2: