单源最短路的综合应用

拓扑排序+迪杰斯特拉:https://www.acwing.com/problem/content/344/

首先我们可以观察到:

1.整个图的形状是一个个通过道路相连的团,而团与团之间通过航线相连。

2.注意到团的非负性,可以用迪杰斯特拉,考虑到航线的单向性,我们可以考虑拓扑排序。

具体的,我们把他们融合在一起:

1.先输入道路扫出连通块。

2.再输入航线确定入度

3.按照拓扑序处理每一个连通块,当其中一个连通块入度为0时取出最小的放入队列中。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second

int t,r,p,s;
int id[250100];
int inf=0x3f3f3f3f;
struct node{
    int dian,w;
};
vector<int> block[250100];
vector<node> edge[250100];
int cnt=0;
int deg[250100];
int dis[250100];
int st[250100];
void dfs(int x,int c){
    id[x]=c;
    block[c].push_back(x);
    for(int i=0;i<edge[x].size();i++){
        int ck=edge[x][i].dian;
        if(id[ck]) continue;
        dfs(ck,c);
    }
}
void dij(){
    priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;//(距离,编号)
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    for(int i=1;i<=cnt;i++){
        if(deg[i]) continue;
        for(int j=0;j<block[i].size();j++){
            int x=block[i][j];
            q.push({dis[x],x});
        }
    }
    while(!q.empty()){
        auto ck=q.top();
        q.pop();
        if(st[ck.y]) continue;
        st[ck.y]=1;
        
        for(int i=0;i<edge[ck.y].size();i++){
            auto fk=edge[ck.y][i];
            if(dis[fk.dian]>dis[ck.y]+fk.w){
                dis[fk.dian]=dis[ck.y]+fk.w;
                if(id[fk.dian]==id[ck.y]) q.push({dis[fk.dian],fk.dian});
            }
        }
        for(int i=0;i<edge[ck.y].size();i++){
            auto fk=edge[ck.y][i];
            if(id[fk.dian]!=id[ck.y]){
                deg[id[fk.dian]]--;
                if(deg[id[fk.dian]]==0){
                    for(int j=0;j<block[id[fk.dian]].size();j++){
                        int x=block[id[fk.dian]][j];
                        q.push({dis[x],x});
                    }
                }
            }
        }
    }
}
int main(){
    cin>>t>>r>>p>>s;
    for(int i=1;i<=r;i++){
        int a,b,c;
        cin>>a>>b>>c;
        edge[a].push_back({b,c});
        edge[b].push_back({a,c});
    }
    //找连通块
    for(int i=1;i<=t;i++){
        if(id[i]==0) dfs(i,++cnt);
    }
    //加入航线
    for(int i=1;i<=p;i++){
        int a,b,c;
        cin>>a>>b>>c;
        edge[a].push_back({b,c});
        deg[id[b]]++;
    }
    //dij
    dij();
    for(int i=1;i<=t;i++){
        if(dis[i]>=inf/2) cout<<"NO PATH"<<endl;
        else cout<<dis[i]<<endl;
    }
}

二分+单源最短路:

首先二分出来一个x,我们想知道一条路上至少有几个是大于X的。我们每一次可以把边权大于X的看成1,比他小的看成0,于是问题就转换成了求单源点的最短路。

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1010, M = 20010;
int n, m, k;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
deque<int> q;
bool st[N];
void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool check(int bound)
{
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);

    q.push_back(1);
    dist[1] = 0;

    while (q.size())
    {
        int t = q.front();
        q.pop_front();

        if (st[t]) continue;
        st[t] = true;

        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i], x = w[i] > bound;
            if (dist[j] > dist[t] + x)
            {
                dist[j] = dist[t] + x;
                if (!x) q.push_front(j);
                else q.push_back(j);
            }
        }
    }
    return dist[n] <= k;
}

int main()
{
    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 = 0, r = 1e6 + 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    if (r == 1e6 + 1) cout << -1 << endl;
    else cout << r << endl;
}

DP+最短路:https://www.acwing.com/problem/content/343/

可以观察到:

从源点1到终点n一定存在一个分界点i,在1\rightarrow i上取到最小值,在i\rightarrow n上取到最大值。

于是我们记maxx[i]表示i\rightarrow n上的最大值,minn[i]表示1\rightarrow i上的最小值,然后我们枚举每一个i即可。

现在我们主要求这个数组,假如图是严格的拓扑序,那么DP转移就十分明显,但是现在的图可能存在回路,直接DP会陷入死循环,于是我们考虑最短路的思想。

我们把距离的概念抽象成上面的两个数组的值,松弛操作也就是maxx[i]=max(w[i],maxx[x])

注意虽然求单源最短路可以迪杰斯特拉,但是他的前提是每一次第一出队的一定是最短的,而显然这个条件在这里不成立(因为后面的可能比当前出对的更小)。

那么SPFA?它的本质是动态规划,每一次在前一次的基础上松弛,保证了走到某一点在i步范围内是正确的,进而不断转移到n步,(这在没有负环的情况下显然是包含所有情况)而在本题中不断走一个环没有意义,也就是不存在负环。因此SPFA是适用的。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[100010];
vector<int> edge[500010];
vector<int> edeg[500010];
int maxx[100010],minn[100010];
int st[100010];
void spfa(int type) 
{
    queue<int> q;
    memset(st, 0, sizeof st);
    if(type==0){
         memset(minn, 0x3f, sizeof(minn));
         minn[1] = a[1];
         q.push(1);
         st[1] = true;
    }
    else{
         memset(maxx, 0xc0, sizeof(maxx));
         maxx[n]=a[n];
         q.push(n);
         st[n]=1;
    }
    while (!q.empty())
    {
        int t = q.front();
        st[t] = false;
        q.pop();
        if(type==0){
        for (int i =0; i <edge[t].size(); i ++)
        {
            int j = edge[t][i];
            if (minn[j] > min(minn[t],a[j]))
            {
                minn[j] = min(minn[t],a[j]);
                if (!st[j])     // 如果队列中已存在j,则不需要将j重复插入
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }}
        else{
            for (int i =0; i <edeg[t].size(); i ++){
                int j=edeg[t][i];
                if(maxx[j]<max(maxx[t],a[j])){
                    maxx[j]=max(maxx[t],a[j]);
                    if (!st[j])     // 如果队列中已存在j,则不需要将j重复插入
                    {
                    q.push(j);
                    st[j] = true;
                    }
                }
            }
        }
    }
}

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=m;i++){
        int x,y,z;
        cin>>x>>y>>z;
        edge[x].push_back(y);
        edeg[y].push_back(x);
        if(z==2){
             edge[y].push_back(x);
             edeg[x].push_back(y);
        }
    }
    spfa(0);
    spfa(1);
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,maxx[i]-minn[i]);
    cout<<res;
}

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值