NOIP2017D1T3 逛公园(最短路+记忆化搜索)

题目传送门:洛谷Loj

题目描述

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

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

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

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

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

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

输入格式:

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

输出格式:

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

输入样例:

2
5 7 2 10
1 2 1
2 4 0
4 5 2
2 3 2
3 4 1
3 5 2
1 5 3
2 2 0 10
1 2 0
2 1 0

输出样例:

3
-1

说明

样例解释

对于第一组数据,最短路为 \(3\)\(1\)\(5\), \(1\)\(2\)\(4\)\(5\), \(1\)\(2\)\(3\)\(5\)\(3\) 条合法路径。

测试数据与约定

对于\(100\)%的数据,\(1≤P≤10^9,1≤ai,bi≤N,0≤ci≤10001≤P≤109,1≤ai,bi≤N,0≤ci≤1000\)
数据保证:至少存在一条合法的路线。

solution

先跑一遍\(dijsktra\)预处理出1到所有点的最短路(在原图上)
\(dist[i]\)表示1到i的最短路(不能到达为INF)

然后我们有两种方法:

  1. 记忆化搜索
  2. 拓扑排序+\(dp\)(暂时不讨论)

我们建反图,从n开始进行记忆化搜索
我们设\(dp[i][j]\)表示从点\(1\)到点\(i\)距离为\(dist[i]+j\)的路径条数

对于一条反边\((u,v,cost)\)
都有\(dp[u][d]=\sum dp[v][dist[u]+d-cost-dist[v]]\)

那么如何判断有没有\(0\)环呢?
如果一个\(dp\)值被经过两次,而且没有被确定,那必定是有\(0\)环了。

那么如何计算\(0\)环对答案产生的贡献呢?
假设\(u\)点在\(0\)环上,现在要计算点\(1\)到点\(u\)距离为\(dist[u]+k\)的答案(dp[u][k])

\(dist[u]+d-cost-dist[v]≥0\) 则不可能从这条边到达点1且满足条件,不合法② 不从它转移
否则如果\(dp[v][dist[u]+d-cost-dist[v]]\)能走到终点②,没有被确定而且是经过了第二次
那么这整个询问答案为\(-1\),否则继续转移 ③

证明①:\(dp[u][d]\)表示从点\(1\)到点\(u\)距离为\(dist[u]+d\)的路径条数
对于任意满足反边\((u,v,cost)\)\(v\),从点\(1\)到点\(u\)距离为\(dist[u]+d\)的路径条数=$\sum $点\(1\)到点\(v\)距离为\(dist[u]+d-cost\)的路径条数(走了这条边,剩下距离减去这条边长)
令:\(k=dist[u]+d-cost\)
\(1\)到点\(u\)距离为\(dist[u]+d\)的路径条数=$\sum $点\(1\)到点\(v\)距离为\(k\)的路径条数
\(dp[u][d]\)=$\sum $点\(1\)到点\(v\)距离为\(k\)的路径条数
令:点\(1\)到点\(v\)距离为\(k\)的路径条数=\(dp[v][nd]\)
\(dp\)的定义,\(dp[v][nd]\)=点\(1\)到点\(v\)距离为\(dist[v]+nd\)的路径条数
得:\(dist[v]+nd=k\)
\(nd=k-dist[v]\)
\(nd=dist[u]+d-cost-dist[v]\)

\(dp[u][d]=\sum dp[v][nd]=\sum dp[v][dist[u]+d-cost-dist[v]]\)

证完。

证明②:
\(dist[u]+d-cost-dist[v]<0\) 则不能从它走到终点(显然,它走过这条边走最短的距离也到不了终点)
\(dist[u]+d-cost-dist[v]≥0\) 则能从它走到终点(显然,它走过这条边至少走最短的距离能到终点)
证完。

证明③:
判断1:若\(dist[u]+d-cost-dist[v]<0\) 不能从它走到终点,它必定不是答案,有0环的话也没有贡献
判断2:\(dp[v][dist[u]+d-cost-dist[v]]\)没有被确定而且是经过了第二次,而且它必定有方案能在≤规定距离走到终点,但它包含在0环内(这就是0环能遇到的充要条件)那么这整个询问答案为\(-1\)
证完。

注意:请勿直接给dp赋初值,那么判0环会漏掉初值点

//原图最短路,反图记忆化搜索
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>,greater<pii> > que;
int T,n,m,k,p,u,v,cost,ans;
int eHead[110000],EHead[110000],dis[110000],dp[110000][61];
bool zeroring,vis[110000],flag[110000][55];
struct E{
    int v,cost,Next;
} edge[210000],Edge[210000];
void init(){
    //邻接表
    memset(eHead,0,sizeof(eHead));
    memset(EHead,0,sizeof(EHead));
    //最短路
    memset(dis,0x3f,sizeof(dis));
    memset(vis,false,sizeof(vis));
    //dp
    memset(dp,-1,sizeof(dp));
}
void addedge(int i,int u,int v,int cost){//加边,原图和反图
    edge[i].v=v; edge[i].cost=cost;
    edge[i].Next=eHead[u]; eHead[u]=i;
    
    Edge[i].v=u; Edge[i].cost=cost;
    Edge[i].Next=EHead[v]; EHead[v]=i;
}
void dijsktra(int s){//最短路
    dis[s]=0;
    que.push(pii(0,s));
    int u,v,cost;
    while (!que.empty()){
        u=que.top().second;
        que.pop();
        if (vis[u]) continue;
        vis[u]=true;
        for (int e=eHead[u];e;e=edge[e].Next){
            v=edge[e].v; cost=edge[e].cost;
            if (!vis[v]&&dis[v]>dis[u]+cost){
                dis[v]=dis[u]+cost;
                que.push(pii(dis[v],v));
            }
        }
    }
}
int dfs(int u,int d){//记忆化搜索
    if (flag[u][d]){//③
        zeroring=true;
        return 0;
    }
    if (~dp[u][d]) return dp[u][d];
    
    flag[u][d]=true; dp[u][d]=0;
    int v,cost,vd;
    for (int e=EHead[u];e&&!zeroring;e=Edge[e].Next){
        v=Edge[e].v; cost=Edge[e].cost;
        vd=dis[u]+d-cost-dis[v];//①
        if (vd>=0) dp[u][d]=(dp[u][d]+dfs(v,vd))%p;//②、③
    }
    flag[u][d]=false;
    if (u==1&&!d) return ++dp[u][d];
    return dp[u][d];    
}
int main(){
    scanf("%d",&T);
    while (T--){
        init();
        scanf("%d%d%d%d",&n,&m,&k,&p);
        for (int i=1;i<=m;i++){
            scanf("%d%d%d",&u,&v,&cost);
            addedge(i,u,v,cost);
        }
        dijsktra(1);
        //dp[1][0]=1;
        zeroring=false;
        ans=dfs(n,0)%p;
        for (int i=1;i<=k&&!zeroring;i++) ans=(ans+dfs(n,i))%p;
        if (zeroring) puts("-1");
        else printf("%d\n",ans);
    }
    return 0;
}
//突发奇想,突然想到反图跑最短路,原图记忆化搜索,原理相同,就不加注释了
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>,greater<pii> > que;
int T,n,m,k,p,u,v,cost,ans;
int eHead[110000],EHead[110000],dis[110000],dp[110000][61];
bool zeroring,vis[110000],flag[110000][55];
struct E{
    int v,cost,Next;
} edge[210000],Edge[210000];
void init(){
    memset(eHead,0,sizeof(eHead));
    memset(EHead,0,sizeof(EHead));
    memset(dis,0x3f,sizeof(dis));
    memset(vis,false,sizeof(vis));
    memset(dp,-1,sizeof(dp));
}
void addedge(int i,int u,int v,int cost){
    edge[i].v=v; edge[i].cost=cost;
    edge[i].Next=eHead[u]; eHead[u]=i;
    
    Edge[i].v=u; Edge[i].cost=cost;
    Edge[i].Next=EHead[v]; EHead[v]=i;
}
void dijsktra(int s){
    dis[s]=0;
    que.push(pii(0,s));
    int u,v,cost;
    while (!que.empty()){
        u=que.top().second;
        que.pop();
        if (vis[u]) continue;
        vis[u]=true;
        for (int e=eHead[u];e;e=edge[e].Next){
            v=edge[e].v; cost=edge[e].cost;
            if (!vis[v]&&dis[v]>dis[u]+cost){
                dis[v]=dis[u]+cost;
                que.push(pii(dis[v],v));
            }
        }
    }
}
int dfs(int u,int d){
    if (flag[u][d]){
        zeroring=true;
        return 0;
    }
    if (~dp[u][d]) return dp[u][d];
    
    flag[u][d]=true; dp[u][d]=0;
    int v,cost,vd;
    for (int e=EHead[u];e&&!zeroring;e=Edge[e].Next){
        v=Edge[e].v; cost=Edge[e].cost;
        vd=dis[u]+d-cost-dis[v];
        if (vd>=0) dp[u][d]=(dp[u][d]+dfs(v,vd))%p;
    }
    flag[u][d]=false;
    if (u==n&&!d) return ++dp[u][d];
    return dp[u][d];    
}
int main(){
    scanf("%d",&T);
    while (T--){
        init();
        scanf("%d%d%d%d",&n,&m,&k,&p);
        for (int i=1;i<=m;i++){
            scanf("%d%d%d",&u,&v,&cost);
            addedge(i,v,u,cost);
        }
        dijsktra(n);
        //dp[1][0]=1;
        zeroring=false;
        ans=dfs(1,0)%p;
        for (int i=1;i<=k&&!zeroring;i++) ans=(ans+dfs(1,i))%p;
        if (zeroring) puts("-1");
        else printf("%d\n",ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/why-1115/p/9674030.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
毕业设计,基于SpringBoot+Vue+MySQL开发的公寓报修管理系统,源码+数据库+毕业论文+视频演示 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本公寓报修管理系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,达到事半功倍的效果。此公寓报修管理系统利用当下成熟完善的Spring Boot框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的MySQL数据库进行程序开发。公寓报修管理系统有管理员,住户,维修人员。管理员可以管理住户信息和维修人员信息,可以审核维修人员的请假信息,住户可以申请维修,可以对维修结果评价,维修人员负责住户提交的维修信息,也可以请假。公寓报修管理系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。 关键词:公寓报修管理系统;Spring Boot框架;MySQL;自动化;VUE
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值