[NOIP2017]逛公园 最短路+拓扑排序+DP

[NOIP2017]逛公园

题目描述

策策同学特别喜欢逛公园。公园可以看成一张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 行,每行一个整数代表答案。

输入输出样例

输入样例#1:
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
输出样例#1:
3
-1

说明

【样例解释1】

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

【测试数据与约定】

对于不同的测试点,我们约定各种参数的规模不会超过如下

测试点编号  T   N   M   K   是否有0边
1 5 5 10 0
2 5 1000 2000 0
3 5 1000 2000 50
4 5 1000 2000 50
5 5 1000 2000 50
6 5 1000 2000 50
7 5 100000 200000 0
8 3 100000 200000 50
9 3 100000 200000 50
10 3 100000 200000 50

对于 100%的数据,1P109,1ai,biN,0ci1000。

数据保证:至少存在一条合法的路线。

题解:本题的思路比较直接,沿着第一感觉一步一步做下去就可以了。

首先肯定要先跑最短路。然后呢?发现K只有50,所以我们一定是要从K入手。所以考虑DP,令f[i][j]表示走到i,多走的长度是j的方案数。(多走 指的是比最短路多的部分的长度)。

但是发现这个DP方程是存在环的,因为最短路径图上的边以及零边都是可以同行转移的(从f[..][j]转移到f[..][j]),所以怎么办呢?拓扑排序呗!

我们将最短路径图上的边以及零边都拿出来跑拓扑排序,然后让这些边在转移时必须沿着拓扑序转移即可。特别地,如果一个零环位于一条从1到n长度<=d+K的路径上,则输出-1即可。

代码有点丑了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <algorithm>
#include <utility>
#define mp(A,B) make_pair(A,B)
using namespace std;
const int maxn=100010;
const int maxm=200010;
int n,m,cnt,K,P,tot,ans;
int to[maxm],next[maxm],val[maxm],head[maxn],dis[maxn],vis[maxn],d[maxn],q[maxn<<1],f[51][maxn];
int to2[maxm],next2[maxm],val2[maxm],head2[maxn],dis2[maxn];
priority_queue<pair<int,int> > pq;
inline int rd()
{
    int ret=0;  char gc=getchar();
    while(gc<'0'||gc>'9') gc=getchar();
    while(gc>='0'&&gc<='9')   ret=ret*10+gc-'0',gc=getchar();
    return ret;
}
inline void add(int a,int b,int c)
{
    to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt;
    to2[cnt]=a,val2[cnt]=c,next2[cnt]=head2[b],head2[b]=cnt++;
}
inline void upd(int &x,int y)
{
    x=(x+y)%P;
}
void work()
{
    n=rd(),m=rd(),K=rd(),P=rd();
    int i,j,k,a,b,c,u;
    memset(head,-1,sizeof(head)),memset(head2,-1,sizeof(head2)),cnt=tot=ans=0;
    for(i=1;i<=m;i++)    a=rd(),b=rd(),c=rd(),add(a,b,c);
    //1
    memset(vis,0,sizeof(vis)),memset(dis,0x3f,sizeof(dis)),memset(d,0,sizeof(d));
    pq.push(mp(0,1)),dis[1]=0;
    while(!pq.empty())
    {
        u=pq.top().second,pq.pop();
        if(vis[u])  continue;
        vis[u]=1;
        for(i=head[u];i!=-1;i=next[i])  if(dis[to[i]]>dis[u]+val[i]) dis[to[i]]=dis[u]+val[i],pq.push(mp(-dis[to[i]],to[i]));
    }
    //2
    memset(dis2,0x3f,sizeof(dis2)),memset(vis,0,sizeof(vis));
    pq.push(mp(0,n)),dis2[n]=0;
    while(!pq.empty())
    {
        u=pq.top().second,pq.pop();
        if(vis[u])  continue;
        vis[u]=1;
        for(i=head2[u];i!=-1;i=next2[i])    if(dis2[to2[i]]>dis2[u]+val2[i]) dis2[to2[i]]=dis2[u]+val2[i],pq.push(mp(-dis2[to2[i]],to2[i]));
    }
    //3
    for(i=1;i<=n;i++)    for(j=head[i];j!=-1;j=next[j])  if(dis[i]+val[j]==dis[to[j]])   d[to[j]]++;
    for(i=1;i<=n;i++)    if(!d[i])   q[++tot]=i;
    for(j=1;j<=tot;j++)
    {
        u=q[j];
        for(i=head[u];i!=-1;i=next[i])  if(dis[u]+val[i]==dis[to[i]])
        {
            d[to[i]]--;
            if(!d[to[i]])   q[++tot]=to[i];
        }
    }
    for(i=1;i<=n;i++)    if(d[i]&&dis[i]+dis2[i]<=dis[n]+K)
    {
        printf("-1\n");
        return ;
    }
    //DP
    memset(f,0,sizeof(f));
    f[0][1]=1;
    for(k=0;k<=K;k++)
    {
        for(i=1;i<=tot;i++)  for(u=q[i],j=head[u];j!=-1;j=next[j])   if(dis[u]+val[j]==dis[to[j]])
        {
            upd(f[k][to[j]],f[k][u]);
        }
        for(i=1;i<=n;i++)    for(j=head[i];j!=-1;j=next[j])  if(dis[i]+val[j]!=dis[to[j]]&&k+dis[i]+val[j]-dis[to[j]]<=K)
        {
            upd(f[k+dis[i]+val[j]-dis[to[j]]][to[j]],f[k][i]);
        }
    }
    for(i=0;i<=K;i++)    upd(ans,f[i][n]);
    printf("%d\n",ans);
}
int main()
{
    //freopen("park.in","r",stdin);
    //freopen("park.out","w",stdout);
    int T=rd();
    while(T--)  work();
    return 0;
}
posted @   CQzhangyu  阅读(3109)  评论(1编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示