最短路径树SPT

对于最短路径树SPT,首先给你一张正权无向图,源点任意,然后要求你删除或者选择全部边的某些边,使得尽可能多的保持两点之间的最短路径,输出边的编号即可

首先从源点跑一遍最短路,在最短路中我们需要记录一个东西,还记得怎么输出路径嘛?是不是在遍历的时候记录一个$pre$数组即可?同样的,我们把这些前驱点保存下来,然后跑完最短路之后,对所有的点和边进行建图,那么我们就可以靠着判断是否为前驱节点来进行建图,由于除了源点之外的每个点只存在一个前驱节点(对于两点之间只有一个条边的,多条边的下面会讲),所以一共是 $n$ 个点, $n-1$ 条边, 这样我们就能构造出一个从源点为根的最短路径树

具体如下:

void dijkstra(int u){
    dist[u]=0,que.push({0,u});
    while(!que.empty()){
        auto now=que.top(); que.pop();
        int x=now.second,y=now.first;
        if(vis[x]) continue; vis[x]=true;
        for(int i=h[x];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>=y+w[i]){
                dist[j]=y+w[i],pre[j]=x;
                que.push({dist[j],j});
            }
        }
    }
    for(int i=1;i<=n;i++)
    for(int j=h[i];~j;j=ne[j]){
        int x=e[j];
        if(pre[x]==i) node[i].push_back(x);
    }
}

那么回到原文,如何进行选择边而使得尽可能多的点保持最短距离呢?我们对最短路径树进行一个搜索,对于树上的任意点,其都是到源点的最短路径,那么我们可以直接做一遍 $dfs$ 或者 $bfs$ 保存即可

void dfs(int u,int fa){
    for(auto x:node[u]){
        if(res.size()>=k) return;
        if(x==fa) continue;
        res.push_back(id[{u,x}]),dfs(x,u);
    }
}

例题:

Problem - 1076D - Codeforces

 

本题就是为板子题,和上述思路一模一样

// LUOGU_RID: 153412284
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+10,mod=1e9+7;
int e[2*N],ne[2*N],idx,h[2*N],w[2*N],pre[N],k,dist[N];
bool vis[N],st[N];
map<pair<int, int>,int>id;
vector<int>node[N],res;
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dijkstra(){
    memset(dist,0x3f,sizeof dist);
    priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>que;
    dist[1]=0,que.push({0,1});
    while(!que.empty()){
        auto now=que.top(); que.pop();
        int x=now.second,y=now.first;
        if(vis[x]) continue; vis[x]=true;
        for(int i=h[x];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>y+w[i]){
                dist[j]=y+w[i],pre[j]=x;
                que.push({dist[j],j});
            }
        }
    }
}
void dfs(int u,int fa){
    for(auto x:node[u]){
        if(res.size()>=k) return;
        if(x==fa) continue;
        res.push_back(id[{u,x}]),dfs(x,u);
    }
}
signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int n,m; cin>>n>>m>>k;
    memset(h,-1,sizeof h);
    for(int i=1;i<=m;i++){
        int a,b,c; cin>>a>>b>>c;
        id[{a,b}]=i,id[{b,a}]=i,add(a,b,c),add(b,a,c);
    }
    dijkstra();
    for(int i=1;i<=n;i++)
        for(int j=h[i];~j;j=ne[j])
            if(pre[e[j]]==i) node[i].push_back(e[j]);
    if(k>n-1) k=n-1;
    cout<<k<<endl,dfs(1,-1);
    for(auto x:res) cout<<x<<' ';
}

CF545E Paths and Trees

本题与上题基本没有区别,要求的是边权和最小的最短路径树,那么我们在遍历的时候只需要求一下边权即可

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,mod=1e9+7;
typedef pair<int,int>pii;
int n,m,k,dist[N],pre[N],ans;
int e[N],ne[N],w[N],idx,h[N];
map<pii,int>id;
bool vis[N];
vector<int>node[N],res;
priority_queue<pii,vector<pii>,greater<pii>>que;
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dijkstra(int u){
    dist[u]=0,que.push({0,u});
    while(!que.empty()){
        auto now=que.top(); que.pop();
        int x=now.second,y=now.first;
        if(vis[x]) continue; vis[x]=true;
        for(int i=h[x];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>=y+w[i]){
                dist[j]=y+w[i],pre[j]=x;
                que.push({dist[j],j});
            }
        }
    }
    for(int i=1;i<=n;i++)
    for(int j=h[i];~j;j=ne[j]){
        int x=e[j];
        if(pre[x]==i) node[i].push_back(x);
    }
}
void dfs(int u,int fa){
    for(auto x:node[u]){
        if(u==fa) continue;
        ans+=(dist[x]-dist[u]),res.push_back(id[{u,x}]),dfs(x,u);
    }
}
signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    memset(h,-1,sizeof h);
    memset(dist,0x3f,sizeof dist);
    for(int i=1;i<=m;i++){
        int a,b,c; cin>>a>>b>>c;
        add(a,b,c),add(b,a,c),id[{a,b}]=id[{b,a}]=i;
    }
    int st; cin>>st;
    dijkstra(st),dfs(st,-1);
    cout<<ans<<endl;
    for(auto x:res) cout<<x<<' ';
}

CF1005F Berland and the Shortest Paths

该题总体思路差不多,不过问你的是有多少种保存路径的选择,所以我们可以记录所有的前驱节点,因为一个点可能有很多前驱节点,比如存在: $1---2---3$ 和 $1---4---3$ 那么 $2和4$ 的前驱节点都是 $3$ 所以 3 的贡献就是 2 ,那么我们直接进行乘法原理,求出可能性的总和,进行dfs即可

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=5e5+10,mod=1e9+7;
typedef pair<int,int> pii;
int n,m,k,dist[N],cnt,res=1;
vector<int>g[N],node[N],pre[N];
map<pii,int>id;
priority_queue<pii,vector<pii>,greater<pii>>que;
bool vis[N],ok[N];
void dijkstra(){
    dist[1]=0,que.push({0,1});
    while(!que.empty()){
        auto now=que.top(); que.pop();
        int x=now.second,y=now.first;
        if(vis[x]) continue; vis[x]=true;
        for(auto u:g[x]){
            if(dist[u]>y+1) dist[u]=y+1,que.push({dist[u],u}),pre[u].push_back(x);
            else if(dist[u]==y+1) pre[u].push_back(x);
        }
    }
}
void dfs(int u){
    if(cnt>=res) return;
    if(u==n+1){
        for(int i=1;i<=m;i++) cout<<ok[i];
        return cout<<endl,cnt++,void();
    }
    for(auto x:pre[u]) ok[id[{x,u}]]=true,dfs(u+1),ok[id[{x,u}]]=false;
}
signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>m>>k;
    memset(dist,0x3f,sizeof dist);
    for(int i=1;i<=m;i++){
        int a,b; cin>>a>>b;
        g[a].push_back(b),g[b].push_back(a),id[{a,b}]=id[{b,a}]=i;
    }
    dijkstra();
    for(int i=2;i<=n;i++){
        if(res*pre[i].size()>k) {res=k;break;};
        res*=pre[i].size();
    }
    cout<<res<<endl,dfs(2);
}

 

posted @ 2024-03-29 18:58  o-Sakurajimamai-o  阅读(25)  评论(0编辑  收藏  举报
-- --