【Luogu P3953】NOIP2017 day1T3 逛公园

题目链接

题目描述

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

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

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

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

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

如果有无穷多条合法的路线,请输出−1。

题解

为什么自己当时那么菜,连这题都毫无头绪

这题明显是个DP,由于和路径长度偏移量有关且K很小,很容易想出状态数组是dp[N][K]。
再来想想状态的含义应该是什么。
我们最后要求的是从1号点出发到达n号点路径长度偏移量不超过k的路径总数。
考虑一条合法路径上的点u,那么u到n号点的最短路偏移量也会小于k。在所有u到n号点的路径中偏移量小于 k-当前已偏移量 的都可以进行贡献。
故我们设dp[u][k]表示从点u到n号点路径长度偏移量不超过k的路径条数,那么答案就是dp[1][k] ,我们可以看出这个状态是很好的。

由于是在图上进行DP,所以用记搜实现会更加方便。转移的话由当前走的边算出这次的偏移量d,继续搜索 dp[v][k-d] 即可。

但我们还要考虑无穷多方案的情况。
可以发现只有当在某条合法路径上出现0环时才有无数条路径。
这时因为走一条0环上的边,新的偏移量一定为0,那么我们就会搜到相同状态,因此只需记录一下是否有相同状态同时被搜到即可。

还有一点需要注意的就是边界的设置。不能只把dp[n][0]设为1,因为dp[n][ ]的任何一个都至少为1,也就是说后面的要都加上1。并且可能有直接的一整个0环出现,要在搜到n号点的时候把他的dp值设为1,不然这个无穷多解的情况就漏了!(WA了好久TAT)

代码如下:

#include
#include
#include
#include
#include
#include
#include
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar())if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
const int N=1e5+100;
const int M=2e5+100;
struct edge{
    int to,next,w;
}a[M],b[M];
int cnt=0;
int head[N];
int he2[N];
int cn=0;
inline void add1(int x,int y,int z){a[++cnt]=(edge){y,head[x],z};head[x]=cnt;}
inline void add2(int x,int y,int z){b[++cn]=(edge){y,he2[x],z};he2[x]=cn;}
queue<int> Q;
int dis[N];bool vis[N];
int t,n,m,k,p;
int dp[N][51];//u到n路径长度不超过 最短路径+k 的方案数
bool in[N][51];//判0环(重复搜相同状态则必为一个0环)
int D;
bool flag;
inline void SPFA()
{
    Q.push(n);
    while(!Q.empty())
    {
        register int u=Q.front();Q.pop();
        vis[u]=0;
        for(register int v,i=head[u];i;i=a[i].next)
        {
            v=a[i].to;
            if(dis[u]+a[i].w>=dis[v]) continue;
            dis[v]=dis[u]+a[i].w;
            if(!vis[v]) {vis[v]=1;Q.push(v);}
        }
    }
}
inline int dfs(int u,int len)
{
    if(flag) return 0;
    if(len<0) return 0;
    if(in[u][len]) return (flag=1);
    if(dp[u][len]!=-1) return dp[u][len];
    dp[u][len]=(u==n? 1:0);//这里再设初状态
    in[u][len]=1;
    for(register int v,i=he2[u];i&&(!flag);i=b[i].next)
    {
        v=b[i].to;register int w=b[i].w;
        dp[u][len]+=dfs(v,len-(dis[v]+w-dis[u]));
        if(dp[u][len]>=p) dp[u][len]-=p;
    }
    in[u][len]=0;
    return dp[u][len];
}
int main()
{
    freopen("park.in","r",stdin);
    freopen("park.out","w",stdout);
    int T=read();
    while(T--)
    {
        n=read();m=read();k=read();Set(head,0);cnt=0;p=read();cn=0;
        Set(dis,127/3);dis[n]=0;register int x,y,z;Set(dp,-1);Set(head,0);Set(he2,0);
        Set(in,0);
        for(register int i=1;i<=m;++i){x=read();y=read();z=read();add2(x,y,z);add1(y,x,z);}
        SPFA();flag=0;
        if(p==1) {puts("0");continue;}
        dfs(1,k);
        if(flag) puts("-1");
        else printf("%d\n",dp[1][k]%p);
    }
}

你可能感兴趣的:(======题解======,——图论———,图上的路径问题,——动态规划——,NOIP,图上的DP)