[NOIP 2017 提高组] 逛公园

题目简述

策策同学特别喜欢逛公园。公园可以看成一张 \(N\) 个点 \(M\) 条边构成的有向图,且没有 自环和重边。其中 \(1\) 号点是公园的入口,\(N\) 号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。

策策每天都会去逛公园,他总是从 \(1\) 号点进去,从 \(N\) 号点出来。

策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果 \(1\) 号点 到 \(N\) 号点的最短路长为 \(d\),那么策策只会喜欢长度不超过 \(d + K\) 的路线。

策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?

为避免输出过大,答案对 \(P\) 取模。

如果有无穷多条合法的路线,请输出 \(-1\)

Analysis

先判无解。显然若存在一个点 \(i\) 使得 \(1\)\(i\) 的最短距离与 \(i\)\(n\) 的最短距离之和与 \(1\)\(n\) 的最短距离相差不超过 \(K\),且 \(i\) 在一个零环上,则我们可以构造出无穷多条合法的路线。

所以我们用 Tarjan 算法求出零环,在用 Dijkstra 算法求出 \(1\)\(i\) 的最短距离 \(f_i\)\(i\)\(n\) 的最短距离 \(g_i\)。然后判断即可。

然后求方案数。设 \(F_{i,t}\) 表示满足 \(1\)\(i\) 的距离为 \(f_i+t\) 的路径数,显然答案为 \(\sum_{i=0}^{K}F_{n,i}\),直接记忆化搜索即可,该部分复杂度为 \(O(mK)\)

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+9,M=2e5+9;

int h[N],nt[M],to[M],w[M],cnt;
int H[N],Nt[M],To[M],W[M],Cnt;
inline void lnk(int x,int y,int d){
    nt[++cnt]=h[x],h[x]=cnt,to[cnt]=y,w[cnt]=d;
    Nt[++Cnt]=H[y],H[y]=Cnt,To[Cnt]=x,W[Cnt]=d;
}

int n,m,K,mo,Zero[N];

priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>> >Heap;
int f[N],g[N]; bool vis[N];
inline void min_dist(){
    memset(f,0x3f,n+2<<2);memset(vis,0,n+5);
    f[1]=0;Heap.push({0,1});
    while(!Heap.empty()){
        int u=Heap.top().second;Heap.pop();
        if(vis[u])continue; vis[u]=1;
        for(int i=h[u],v;i;i=nt[i])
            if(f[v=to[i]]>f[u]+w[i])
                f[v]=f[u]+w[i],Heap.push({f[v],v});
    }
    memset(g,0x3f,n+2<<2);memset(vis,0,n+5);
    g[n]=0,Heap.push({0,n});
    while(!Heap.empty()){
        int u=Heap.top().second;Heap.pop();
        if(vis[u])continue; vis[u]=1;
        for(int v,i=H[u];i;i=Nt[i])
            if(g[v=To[i]]>g[u]+W[i])
                g[v]=g[u]+W[i],Heap.push({g[v],v});
    }
    assert(f[n]==g[1]);
    //for(int i=1;i<=n;i++)printf("%d ",f[i]); puts("");
    //for(int i=1;i<=n;i++)printf("%d ",g[i]); puts("");
}

int dfn[N],low[N],stk[N],co[N],dft,tot;
vector<int>Tmp;
inline void Tarjan(int u){
    dfn[u]=low[u]=++dft,stk[++tot]=u;
    for(int i=h[u],v;i;i=nt[i])if(w[i]==0){
        if(!dfn[v=to[i]])Tarjan(v),low[u]=min(low[u],low[v]);
        else if(!co[v]) low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        Tmp.clear();
        for(;;){
            Tmp.push_back(stk[tot]);
            co[stk[tot]]=1;
            if(stk[tot--]==u)break;
        }
        if(Tmp.size()>1)for(auto i:Tmp)Zero[i]=1;
    }
}
inline bool judge(){
    for(int i=1;i<=n;i++)
        Zero[i]=dfn[i]=low[i]=stk[i]=co[i]=0;
    dft=tot=0;
    for(int i=1;i<=n;i++)if(!dfn[i])Tarjan(i);
    //for(int i=1;i<=n;i++)cout<<Zero[i]<<' ';puts("");
    for(int i=1;i<=n;i++)
        if(Zero[i]&&f[i]+g[i]<=K+f[n])return true;
    return false;
}

int F[51][N];
inline int dp(int x,int lim){
    if(F[lim][x]!=-1)return F[lim][x];
    int&ans=F[lim][x];ans=0;
    for(int i=H[x],p;i;i=Nt[i])if(!Zero[p=To[i]]){
        //cout<<x<<' '<<p<<endl;
        int t=f[p]+W[i]-f[x];
        if(t<=lim)(ans+=dp(p,lim-t))%=mo;
    }
    //cout<<lim<<' '<<x<<' '<<ans<<endl;
    return ans;
}

int main(){
    //freopen("a.in","r",stdin);
    int T;scanf("%d",&T);
    for(;T--;){
        scanf("%d%d%d%d",&n,&m,&K,&mo);
        cnt=Cnt=0;
        for(int i=1;i<=n+1;i++)h[i]=H[i]=0;
        for(int i=1,a,b,c;i<=m;i++)
            scanf("%d%d%d",&a,&b,&c),lnk(a,b,c);
        min_dist();
        if(judge()){puts("-1");continue;}
        for(int i=0;i<=K;i++)
            for(int j=1;j<=n;j++)
                F[i][j]=-1;
        F[0][1]=1;
        int ans=0;
        for(int lim=0;lim<=K;lim++)
            (ans+=dp(n,lim))%=mo;
        printf("%d\n",ans);
    }
    //cout<<(int)2e9<<endl;
    return 0;
}
posted @ 2025-06-16 11:38  fzrcy  阅读(19)  评论(0)    收藏  举报