洛谷P3953 逛公园

洛谷P3953 逛公园

题目描述

策策同学特别喜欢逛公园。公园可以看成一张\(N\)个点\(M\)条边构成的有向图,且没有自环和重边。其中1号点是公园的入口,\(N\)号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从1号点进去,从\(N\)号点出来。
策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果1号点 到\(N\)号点的最短路长为\(d\),那么策策只会喜欢长度不超过\(d+K\)的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?
为避免输出过大,答案对\(P\)取模。
如果有无穷多条合法的路线,请输出\(-1\)

输入输出格式

输入格式:

第一行包含一个整数 \(T\), 代表数据组数。
接下来\(T\)组数据,对于每组数据: 第一行包含四个整数 \(N,M,K,P\),每两个整数之间用一个空格隔开。
接下来\(M\)行,每行三个整数\(a_i,b_i,c_i\),代表编号为\(a_i,b_i\)​的点之间有一条权值为 \(c_i\)​的有向边,每两个整数之间用一个空格隔开。

输出格式:

输出文件包含 \(T\) 行,每行一个整数代表答案。

思路

记忆化搜索
首先从\(N\)开始反向求一遍最短路,这样就得到了每一个点到\(N\)号节点的最短路径长度,记\(Dis_i\)\(i\)号节点到\(N\)号节点的最短路径长度。
\(F_{i,j}\)表示第\(i\)号节点在路线长度为\(d+k-j\)时的路线条数,或者说是第\(i\)号节点还可以浪费\(j\)个单位距离时的路线条数。
那么转移方程如下:\[F_{i,j}=\sum_{k \in \{ k| (i,k) \in E \}}{F_{k,j+Dis_i-(Dis_k+W_{i,k})}}\]
因此,本问题也就可以用记忆化搜索求了
再考虑有无数解的情况,很显然,当且仅当出现零环的时候回有无数解。
也就是说,我们只要在记忆化搜索之前找一下有没有零环,有的话直接输出\(-1\)没有的话在做记忆化搜索
可以用tarjan缩点判断零环
先将图中所有长度大于\(0\)的边都去掉,然后跑tarjan,如果缩出来的强连通分量中有顶点数大于\(1\)的,那么就说明有零环。

CODE

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
#define MAXN 100010
#define MAXM 200010
struct R{
    int id,dis;
    R(){}
    R(int id,int dis):id(id),dis(dis){}
    bool operator < (const R &a) const {
        return dis>a.dis;
    }
};
struct Node{
    int u,v,w;
    Node(){}
    Node(int u,int v,int w):u(u),v(v),w(w){};
}p[MAXM],revp[MAXM];
int head[MAXN],revhead[MAXN],Next[MAXM],revNext[MAXM],dis[MAXN],cnt[MAXN][55],dfn[MAXN],low[MAXN],color[MAXN];
bool vis[MAXN];
int i,j,k,m,n,x,y,u,v,w,tot,revtot,times,colnum,t,r,mod,mindis;
stack<int> mstack;
char readc;
bool flag;
void read(int &n){
    while((readc=getchar())<48||readc>57);
    n=readc-48;
    while((readc=getchar())>=48&&readc<=57) n=n*10+readc-48;
}
void addNode(int u,int v,int w){
    p[++tot]=Node(u,v,w);
    Next[tot]=head[u],head[u]=tot;
    revp[++revtot]=Node(v,u,w);
    revNext[revtot]=revhead[v],revhead[v]=revtot;
}
bool relax(int u,int v,int w){
    if(dis[u]+w<dis[v]){
        dis[v]=dis[u]+w;
        return true;
    }
    return false;
}
void tarjan(int src){
    low[src]=dfn[src]=++times;
    vis[src]=true;
    mstack.push(src);
    for(int i=head[src];i+1;i=Next[i]){
        if(p[i].w) continue;
        if(!dfn[p[i].v]){
            tarjan(p[i].v);
            low[src]=min(low[src],low[p[i].v]);
        }else{
            if(vis[p[i].v]) low[src]=min(low[src],dfn[p[i].v]);
        }
    }
    if(low[src]==dfn[src]){
        int tmp;
        colnum++;
        do{
            tmp=mstack.top(); mstack.pop();
            vis[tmp]=false;
            color[colnum]++;
        }while(tmp!=src);
    }
}
void dijkstra(int src){
    memset(dis,0x3f,sizeof(dis));
    priority_queue<R> mque;
    dis[src]=0;
    mque.push(R(src,0));
    while(!mque.empty()){
        R tmp=mque.top(); mque.pop();
        if(tmp.dis>dis[tmp.id]) continue;
        for(int i=revhead[tmp.id];i+1;i=revNext[i])
            if(relax(revp[i].u,revp[i].v,revp[i].w)){
                mque.push(R(revp[i].v,dis[revp[i].v]));
            }
    }
}
int dfs(int src,int cost){
    if(cnt[src][cost]+1) return cnt[src][cost];
    cnt[src][cost]=0;
    if(src==n) cnt[src][cost]++;
    for(int i=head[src];i+1;i=Next[i]){
        if((p[i].w+dis[p[i].v])-dis[src]<=cost){
            cnt[src][cost]+=dfs(p[i].v,cost+dis[src]-p[i].w-dis[p[i].v]);
            cnt[src][cost]%=mod;
        }
    }
    return cnt[src][cost];
}
int main(){
    read(t);
    for(r=1;r<=t;r++){
        memset(head,-1,sizeof(head));
        memset(revhead,-1,sizeof(revhead));
        memset(vis,0,sizeof(vis));
        memset(color,0,sizeof(color));
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        memset(cnt,-1,sizeof(cnt));
        tot=revtot=-1;
        times=colnum=0;
        read(n),read(m),read(k),read(mod);
        for(i=1;i<=m;i++){
            read(u),read(v),read(w);
            addNode(u,v,w);
        }
        for(i=1;i<=n;i++)
            if(!dfn[i]) tarjan(i);
        flag=false;
        for(i=1;i<=colnum;i++){
            if(color[i]>1){
                flag=true;
                break;
            }
        }
        if(flag){
            printf("-1\n");
            continue;
        }
        dijkstra(n);
        mindis=dis[1];
        printf("%d\n",dfs(1,k));
    }
    return 0;
}

转载于:https://www.cnblogs.com/linxif2008/p/10340187.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值