Loading

洛谷P3096

Description

给定一张图,\(n\) 个点,\(m\) 条单向边。

每条单向边至少有一个端点是枢纽,总共有 \(k\) 个枢纽。

给出若干对形如 x y 的询问,表示询问能否从 \(x\) 点走到到 \(y\) 点。

求出能成立的询问的个数,以及所有询问的最短路程和。

Solution

发现如果每个点都跑一遍最短路,问题就可以直接解决。

但是 \(n\le 20000\),数组显然开不下。

另外有路径必须经过至少一个枢纽的限制,所以只对起点跑最短路也是行不通的。

已知每条边的至少一个端点为枢纽,且最多 \(k\) 个,\(k\le 200\)

所以我们对每个枢纽跑一遍最短路,就可以掌握全图的情况。

对于每组询问:

  • 如果起点是枢纽,已经跑过最短路了,可以直接判断。

  • 如果起点不是枢纽,那么其能遍历到的点一定是枢纽,而对于枢纽已经处理完毕,所以可以取起点到枢纽的边权与枢纽到终点的最短路之和的最小值。

另外注意,虽然 \(k\le 200\),但是给出的枢纽编号范围是 \([1,20000]\),如果直接将其编号作为数组的一维会开不下。

根据以上信息,本题可以完成。

Code

#include<queue>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 40010
#define INF 0x3f3f3f3f
#define int long long

using namespace std;

bool vis[maxn];
int n,m,k,q,tot,ans,cnt,pre[maxn];
int Dis[maxn],head[maxn],path[210][maxn];
struct edge{int fr,to,dis,nxt;}e[maxn];

inline int read(){
    int s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
    return s*w;
}

void add(int fr,int to,int dis){
    e[++tot]=(edge){fr,to,dis,head[fr]};head[fr]=tot;
}

void Dijk(int s){
    priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
    memset(Dis,INF,sizeof Dis);memset(vis,0,sizeof vis);q.push(make_pair(0,s));Dis[s]=0;
    while(!q.empty()){
        int u=q.top().second;q.pop();
        if(vis[u]) continue;vis[u]=1;
        for(int i=head[u];i;i=e[i].nxt){
            int to=e[i].to;
            if(Dis[to]>Dis[u]+e[i].dis)
                Dis[to]=Dis[u]+e[i].dis,
                q.push(make_pair(Dis[to],to));
        }
    }
    for(int i=1;i<=n;i++) path[pre[s]][i]=Dis[i];
}

signed main(){
    memset(path,INF,sizeof path);
    n=read();m=read();k=read();q=read();     
    for(int i=1,fr,to,dis;i<=m;i++){
        fr=read();to=read();dis=read();
        add(fr,to,dis);
    }
    for(int i=1,pos;i<=k;i++)
        pos=read(),pre[pos]=i,Dijk(pos);
    for(int i=1,fr,to;i<=q;i++){
        fr=read();to=read();
        if(pre[fr]&&path[pre[fr]][to]<path[0][0]) cnt++,ans+=path[pre[fr]][to];
        else{
            int now=INF;
            for(int j=head[fr];j;j=e[j].nxt){
                int v=e[j].to;
                now=min(e[j].dis+path[pre[v]][to],now);
            }
            if(now!=INF&&now<path[0][0]) ans+=now,cnt++;
        }
    }
    printf("%lld\n%lld",cnt,ans);
    return 0;
} 
posted @ 2021-04-29 14:47  KnightL  阅读(47)  评论(0编辑  收藏  举报