0基础刷图论最短路 3(从ATcoder 0分到1800分)

AT最短路刷题3(本文难度rated 1200~ 1400)

题目来源:Atcoder
题目收集:
https://atcoder-tags.herokuapp.com/tags/Graph/Shortest-Path
(里面按tag分类好了Atcoder的所有题目,类似cf)
(访问需要魔法)

这算是一个题单,各位有兴趣可以按照这个顺序来刷。
我的代码仅供参考。
会提示关键性质和步骤。 部分有注释。
洛谷、知乎、可以搜到题解。

1-身体バランス

这道题其实就是求两边dijkstra
一遍正图
一遍反图

然后枚举每个点,
1 --> i  的路径长度  == i--> n 的路径长度
并且, dist1[i]+dist2[i] == dist1[n]

然后找有没有这样的点就行了。

   贴答案ing
#include <bits/stdc++.h>

#define rep(i,n) for(int i=0;i<(n);i++)

using namespace std;

template<class T> struct edge{
	int to;
	T wt;
	edge(int to,const T& wt):to(to),wt(wt){}
};
template<class T> using weighted_graph=vector<vector<edge<T>>>;

template<class T>
vector<T> Dijkstra(const weighted_graph<T>& G,int s){
	const T INF=numeric_limits<T>::max();
	int n=G.size();
	vector<T> d(n,INF); d[s]=0;
	priority_queue<pair<T,int>> Q; Q.emplace(0,s);
	while(!Q.empty()){
		T d0=-Q.top().first;
		int u=Q.top().second; Q.pop();
		if(d0>d[u]) continue;
		for(const auto& e:G[u]){
			int v=e.to;
			if(d[v]>d[u]+e.wt) d[v]=d[u]+e.wt, Q.emplace(-d[v],v);
		}
	}
	return d;
}

const int INF=1<<29;

int main(){
	int n,m,s,t; scanf("%d%d%d%d",&n,&m,&s,&t); s--; t--;
	weighted_graph<int> G(n);
	rep(i,m){
		int u,v,c; scanf("%d%d%d",&u,&v,&c); u--; v--;
		G[u].emplace_back(v,c);
		G[v].emplace_back(u,c);
	}

	auto d1=Dijkstra(G,s);
	auto d2=Dijkstra(G,t);
	rep(u,n) if(d1[u]<INF && d1[u]==d2[u]) return printf("%d\n",u+1),0;
	puts("-1");

	return 0;
}

2-Road Reduction

最短路

https://atcoder.jp/contests/abc252/tasks/abc252_e

一张图,有N个点。
M条边。
现在我们要删除一些边,最终会剩下N-1条边。

然后,求出:
1到每个城市的最短路径

这个问题只是看上去很唬人。

我们只要知道一个事情:
如果一张图是连通的,那么它的边最少都要是N-1

由于,最短路只会比这个多,不会比这个少。
所以,直接跑最短路。
拓展到谁就是谁。
    
    #include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define PII pair<int,pair<int,int>>
#define INF 1e18
const int N = 2e5+7;
struct node{
    int v;
    int w;
    int id; //edge编号
};
struct edge{
    int u;
    int id;
    int step;
    bool operator<(edge b)const{
        return step>b.step;
    }
};
vector<node> g[N];
int dist[N];
int flag[N];
int n,m;

void dijkstra(){
    for(int i=1;i<=n;i++)dist[i]=INF;
    dist[1]=0;
    priority_queue<edge> q;
    q.push(edge{1,-1,0});

    while(q.size()){
        int u = q.top().u;
        int id = q.top().id;
        q.pop();
        if(flag[u])continue;
        flag[u]=1;

        if(id!=-1){
            cout<< id<<' ';
        }

        for(auto i:g[u]){
            int v = i.v;
            int w = i.w;
            int id = i.id;
            if(dist[v]>dist[u]+w){
                dist[v]=dist[u]+w;
                q.push(edge{v,id,dist[v]});
            }
        }
    }
}
void slove(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        g[u].push_back(node{v,w,i});
        g[v].push_back(node{u,w,i});
    }
    dijkstra();
}

signed main(){
    slove();
}

3 - Swap Places

https://atcoder.jp/contests/abc289/tasks/abc289_e]

BFS 存两个点状态

/*
一种新类型
想过双搜,但其实这比双搜简单。
因为他有限制条件。

1、它们两个移动到的顶点必须是不同颜色
2、它们两个要同时到达起点和终点

然后我们再思考一下flag这么写?
是一个点不能重复,还是一个状态不能重复。
显然是一个状态,也就是两个人所在的位置不能重复入队


 我们bfs可不可以回头或者一个人停下来等?
 为什么要回头?
 假如一方更快到达,一方更慢到达。需要有人停下来等下。
 显然这种情况是不会发生的...
 因为它们两个是同时移动,而不是先后移动。
 所以只要存在路径它们能一直往前走,那么就可以同时到达。
 所以我们的flag函数,
 记录的是:一个状态不可到达两次

*/

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define PII pair<int,int>
#define endl "\n"
#define INF 1e18
#define int long long
const int N = 2001; // 1e6 + 5

struct node{
    int n1;
    int n2;
    int step;
};
int color[N];
bool flag[N][N];


void solve() {
    memset(color,0,sizeof color);
    memset(flag,0,sizeof flag);
    vector<int> g[N];

    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>color[i];
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    queue<node> q;
    q.push(node{1,n,0});
    while(q.size()){
        int n1 = q.front().n1;
        int n2 = q.front().n2;
        int step = q.front().step;
        q.pop();
        if(flag[n1][n2])continue;
        flag[n1][n2]=1;

        if(n1==n and n2==1){
            cout<<step<<endl;
            return;
        }

        for(auto i:g[n1]){
            for(auto j:g[n2]){
                if(color[i]!=color[j])
                q.push({i,j,step+1});

            }
        }

    }
    cout<<-1<<endl;

}
signed main () {
    std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t;
    cin>> t;
    while(t --)
        solve();
}

4-Come Back Quickly

https://atcoder.jp/contests/abc191/tasks/abc191_e

dijkstra

/*
对于每个城镇,我们需要找到一个城镇:

1、从起点出发走到那里还能再回来
2、出发+回来的时间最短

N=2000
跑2000遍最短路。

对于每个起点  ,更新 dist[i][j]
跑完之后,
对于每个起点,找到一个点使得  dist[i][j]+dist[j][i] 最短
如果i==j 那么只加一遍。

关于输入:
对于同一对点,只取最短的路。
一开始全部初始为INF

*/

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18
const int N =2001;
int n,m;
struct node{
    int v;
    int w;
};
vector<node> g[N];
int dist[N];
bool flag[N];
int d[N][N];

void dijkstra(int start){
    for(int i=1;i<=n;i++) dist[i]=INF;
    for(int i=1;i<=n;i++) flag[i]=0;
    dist[start]=0;
    priority_queue<PII,vector<PII>,greater<PII>> q;
    q.push({0,start});
    while(q.size()){
        int u = q.top().second;
        q.pop();
        if(flag[u])continue;
        flag[u]=1;

        for(auto i:g[u]){
            int v =i.v;
            int w =i.w;
            if(dist[v]>dist[u]+w){
                dist[v]=dist[u]+w;
                d[start][v] = min(d[start][v],dist[v]);
                q.push({dist[v],v});
            }
        }
    }

}
void slove(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            d[i][j]=INF;

    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        d[u][v]=min(d[u][v],w);
    }

    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i!=j){
                if(d[i][j]!=INF)g[i].push_back(node{j,d[i][j]});
            }
        }
    }

    for(int i=1;i<=n;i++){
        dijkstra(i);
    }

    for(int i=1;i<=n;i++){
        int ans = INF;
        for(int j=1;j<=n;j++){
            if(i==j){
                ans = min(ans,d[i][j]);
            }
            else{
                ans = min(ans,d[i][j]+d[j][i]);
            }
        }
        if(ans==INF)cout<<-1<<endl;
        else cout<<ans<<endl;

    }

}

signed main(){
    slove();
}

5-正直者の高橋くん

https://atcoder.jp/contests/abc021/tasks/abc021_c

dp+dijkstra

/*
新的题型:

求起点到终点,的最短路有多少条。

考虑dp

设dp[i][j]表示从起点出发,走到i,且距离为j的路的数量

假设结尾是end,
答案是 dp[end][dist];

那么从所有能扩展到end的点中
dp[end][dist] += dp[i][dist-1]


我们考虑初始是怎么得到状态的:

从起点开始扩展:

dp[v][j] += dp[u][j-1]

初始化就是
dp[start][0]=1;
*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18
const int mod = 1e9+7;
const int N = 101;
int dp[101][1000]; //答案
int dist[101]; //最短路
bool flag[101];
vector<int> g[N];

int a,b; //起点终点
int n,m;

void dijkstra(){
    for(int i=1;i<=n;i++)dist[i]=INF;
    dist[a]=0;
    dp[a][0]=1;
    priority_queue<PII,vector<PII>,greater<PII>> q;
    q.push({0,a});

    while(q.size()){
        int u = q.top().second;
        q.pop();
        if(flag[u])continue;
        flag[u]=1;
        for(auto i:g[u]){
            if(dist[i]>=dist[u]+1){
                dist[i]=dist[u]+1;
                dp[i][dist[i]]=( dp[i][dist[i]]+dp[u][dist[u]])%mod;
                q.push({dist[i],i});
            }
        }
    }

    cout<<dp[b][dist[b]]%mod<<endl;
}
void slove(){
    cin>>n;
    cin>>a>>b;
    cin>>m;
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dijkstra();

}

signed main(){
    slove();
}

6-joisino’s travel

https://atcoder.jp/contests/abc073/tasks/abc073_d

floyd + 全排列

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

int n,m,R,dist[300][300],r[10];

int main()
{
    memset(dist,0x3f,sizeof dist);
    int Min,ans;
    cin>>n>>m>>R;
    for(int i=0;i<R;i++)
        cin>>r[i];
    for(int i=0;i<m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        dist[x][y]=dist[y][x]=z;
    }
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(dist[i][k]+dist[k][j]<dist[i][j])
                    dist[i][j]=dist[i][k]+dist[k][j];
    sort(r,r+R);
    ans=0x3f3f3f3f;
    do
    {
        Min=0;
        for(int i=0;i<R-1;i++)
            Min+=dist[r[i]][r[i+1]];
        ans=min(Min,ans);
    }while(next_permutation(r,r+R));
    cout<<ans;
}

7-Traveler

https://atcoder.jp/contests/abc197/tasks/abc197_e

/*
这是一个新问题:
如果你必须要走完所有点 or 必须走完所选中的点。
你的最短路是多少?

建图:

如果小球ID一样,那么两者之间建立双向边。

标记数组:
标记每个球

其实最终这是一条链。
因为对于id不一样的小球,我们是线性升序的,也就是一条链

but... 对于ID一样的小球,我们必须采用特定的顺序。

使之变成一条链。


对于id一样的小球,我们只关心分布两端的球。
中间的球在拣两端的球的路上会被捡走。

而对于同一种id的小球,我们最后拣左端 还是 拣右段 是不知道的           。

这需要跟下一种要捡的id有关。
而。。下一种要捡的id,也是找左端和右段。

显然,状态有点太多了。
所以考虑dp

设dp[i][2]表示当前是 id = i的小球,我们最后一次是捡 0左边/1右边 的最短路

假设序列已经排好序:

捡左边: abs(now - d[1]) + abs(d[k] - d[1])
显然,我们还需要一个中间变量记录每一种id开始捡的时候的坐标。

捡右边:abs(now - d[k]) + abs(d[1]-d[k]) ;

*/

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18
const int N = 2e5+7;
int dp[N][2];
set<int> color;
vector<int> g[N];
void slove(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        int x,c;
        cin>>x>>c;
        color.insert(c);
        g[c].push_back(x);
    }

    for(auto i:color){
        sort(g[i].begin(),g[i].end());
    }

    int now = 0;
    for(auto i = color.begin() ; i!=color.end();i++){
        int id = *i;
        auto it = i;
        auto is = it;
        if(it!=color.begin()){
                is--;
                int pre = *is; //上一次的小球id
                //当前id的小球,最后一次收集是左边:
                    // 上一次的小球收集最后是在左边:
                int temp1 = dp[pre][0] + abs(g[pre][0]-g[id][g[id].size()-1]) + abs(g[id][0]-g[id][g[id].size()-1]);
                    //上一次的小球收集在右边
                int temp2 = dp[pre][1] + abs(g[pre][g[pre].size()-1]-g[id][g[id].size()-1]) + abs(g[id][0]-g[id][g[id].size()-1]);

                dp[id][0] = min(temp1,temp2);

                //当前id的小球,最后一次收集在右边:
                    // 上一次的小球收集最后是在左边:
                temp1 = dp[pre][0] + abs(g[pre][0]-g[id][0]) + abs(g[id][0]-g[id][g[id].size()-1]);
                    //上一次的小球收集在右边
                temp2 = dp[pre][1] + abs(g[pre][g[pre].size()-1]-g[id][0]) + abs(g[id][0]-g[id][g[id].size()-1]);

                dp[id][1] = min(temp1,temp2);
        }
        else{

                //当前id的小球,最后一次收集是左边:
                    // 上一次的在起点:
                int temp1 = 0 + abs(0-g[id][g[id].size()-1]) + abs(g[id][0]-g[id][g[id].size()-1]);
                    //上一次的在起点:
                int temp2 = 0 + abs(0-g[id][g[id].size()-1]) + abs(g[id][0]-g[id][g[id].size()-1]);

                dp[id][0] = min(temp1,temp2);

                //当前id的小球,最后一次收集在右边:
                    // 上一次的在起点:
                temp1 = 0 + abs(0-g[id][0]) + abs(g[id][0]-g[id][g[id].size()-1]);
                    //上一次的在起点:
                temp2 = 0 + abs(0-g[id][0]) + abs(g[id][0]-g[id][g[id].size()-1]);

                dp[id][1] = min(temp1,temp2);
        }


    }
    // 加上回到起点的距离
    cout<<min(dp[*color.rbegin()][1]+abs(g[*color.rbegin()][g[*color.rbegin()].size()-1]),dp[*color.rbegin()][0] + abs(g[*color.rbegin()][0]) )<<endl;


}

signed main(){
    slove();
}

8- Merge Set

https://atcoder.jp/contests/abc302/tasks/abc302_f

警钟长鸣:N要开大点

/*
一共有N个集合
每个集合包含图内的一些点。

当前仅当,两个集合内有交点,那么我们可以将这两个集合合并

集合内部是互通的。

我们现在要从1,到M

请问需要连接多少集合。

我们可以在每个集合内部连接一条边权为0的点。

在每个集合间连接一条边权为1的点。

然后跑一遍最短路。

问题是:
如何在集合之间连边?

考虑集合之间是如何互通的?

这题算是一个套路的建图题。

对于这种集合间连边的问题:
我们可以给每个集合都编号。

这个编号不能和现在有的点重复。
所以可以考虑i+M

然后把每个集合内部的点,都和这个集合相连。

点到集合的边为0
集合到点的边为1

答案就是最短路-1
*/

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18

struct node{
    int v;
    int w;
};
struct point{
    int u;
    int step;
    bool operator <(point b)const{
        return step>b.step;
    }
};

const int N = 5e6+6;
int dist[N];
int flag[N];
vector<node> g[N];
void slove(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int len;
        cin>>len;
        while(len--){
            int v;
            int u = i+m;
            cin>>v;
            g[v].push_back(node{u,1});
            g[u].push_back(node{v,1});
        }
    }

    priority_queue<point> q;
    q.push(point{1,0});
    while(q.size()){
        int u  = q.top().u;
        int step = q.top().step;
        q.pop();
        if(flag[u])continue;
        flag[u]=1;
        if(u==m){
            cout<<step/2-1<<endl;
            return;
        }
        for(auto i : g[u]){
             int v = i.v;
             int w = i.w;
             q.push(point{v,w+step});
        }
    }

    cout<<-1<<endl;

}

signed main(){
    slove();
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

louisdlee.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值