把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷3953】逛公园(最短路+记忆化搜索)

点此看题面

大致题意: 有一张有\(N\)个点和\(M\)条边组成的有向图,若从\(1\)号点到\(N\)号点的最短路径长度为\(d\),问有多少条从\(1\)号点到\(N\)号点的路径长度不超过\(d+K\)。若有无数条输出\(-1\)

第一步:最短路

既然要求的是长度不超过\(d+K\)的路径条数,显然我们要先求出\(d\),因此就需要跑一遍最短路

但是,最短路怎么跑也是有学问的。

第一个最容易想到的办法应该是直接从\(1\)号节点开始跑最短路吧。但由于是有向图,且不保证联通,因此某些节点有可能无法到达\(N\)号节点,将它们一起操作就会影响答案(数据太水,好像没什么影响时间效率(实践证明这样的写法会\(TLE\)但也有可能是因为我的代码不够优秀

针对这样的问题,我们就需要从\(N\)号节点开始,在反向边上跑最短路,这样就没问题了。

下文我们用\(dis_i\)来表示\(i\)号节点到\(N\)号节点的距离。

如何记录状态

我们可以考虑用\(f_{i,j}\)来表示 \(1\)号节点出发至\(i\)号节点、还能多走的路程为\(j\) 的方案数。

然后,就不难想到用两种方法来求解:动态规划记忆化搜索

我比较弱,写不来动态规划,因此用的是记忆化搜索。

记忆化搜索

假设当前状态为\((u,val)\)(从\(1\)号节点出发至\(u\)号节点、还能多走的路程为\(val\)),我们考虑如何转移到下一个状态。

对于一条从\(u\)\(v\)且边权为\(w\)的有向边,不难想到,如果选择这条边,需要多走的路程应该是\(dis_v+w-dis_u\)(即现在能走的最短路减去原先的最短路),那么就可以得出\(f_{u,val}=\sum f_{v,val-(dis_v+w-dis_u)}\)

边界条件为\(f_{N,0}=1\)

这样就\(OK\)了。

对于无数解的情况

那么什么样的情况会无数解呢?

不难想到,就是在从\(1\)号节点到\(N\)号节点的一条长度\(≤d+K\)路径上出现了\(0\)环。

我们可以用\(vis_{i,j}\)来记录当前状态是否访问过,就可以判断是否出现\(0\)环了。

代码

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=y)>=MOD&&(x-=MOD))
#define N 100000
#define M 200000
#define K 50 
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=z)
#define rev_add(x,y,z) (rev_e[++rev_ee].nxt=rev_lnk[x],rev_e[rev_lnk[x]=rev_ee].to=y,rev_e[rev_ee].val=z)
using namespace std;
int n,m,k,MOD,ee=0,rev_ee=0,lnk[N+5],rev_lnk[N+5];
struct edge
{
    int to,nxt,val;
}e[M+5],rev_e[M+5];
class FIO
{
    private:
        #define Fsize 100000
        #define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
        #define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
        int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
    public:
        FIO() {FinNow=FinEnd=Fin;}
        inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));x*=f;}
        inline void read_char(char &x) {while(isspace(x=tc()));}
        inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
        inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
        inline void write_char(char x) {pc(x);}
        inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
        inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
class Class_SPFA//SPFA求最短路
{
    private:
        int Inqueue[N+5];
        queue<int> q;
    public:
        int dis[N+5];
        inline void Rev_GetAns(int s)//反向边上跑最短路
        {
            register int i,k,v;
            for(i=1;i<=n;++i) dis[i]=INF;dis[s]=0,Inqueue[s]=1,q.push(s);
            while(!q.empty())
            {
                for(Inqueue[k=q.front()]=0,q.pop(),i=rev_lnk[k];i;i=rev_e[i].nxt)
                {
                    if(dis[k]+rev_e[i].val<dis[v=rev_e[i].to])
                    {
                        dis[v]=dis[k]+rev_e[i].val;
                        if(!Inqueue[v]) q.push(v),Inqueue[v]=1;
                    }
                }
            } 
        }
}SPFA;
class Class_DFS//记忆化搜索
{
    private:
        int vis[N+5][K+5],f[N+5][K+5];
    public:
        inline void Clear() {for(register int i=0,j;i<=n;++i) for(j=0;j<=k;++j) f[i][j]=-1;}//清空数组,因为有多组数据
        inline int GetAns(int x,int v)//记忆化搜索
        {
            if(vis[x][v]) return -1;//如果访问过当前状态,就说明出现了0环,返回-1
            if(~f[x][v]) return f[x][v];//记忆化
            register int i,t,res;
            for(vis[x][v]=1,f[x][v]=0,i=lnk[x];i;i=e[i].nxt)//枚举相邻节点
            {
                if((t=v-(SPFA.dis[e[i].to]+e[i].val-SPFA.dis[x]))<0||t>k) continue;//如果越界,就跳过
                if(!~(res=GetAns(e[i].to,t))) return (void)(vis[x][v]=0),-1;//如果无数解,返回-1(记得在return之前将访问标记删除,为了这个小细节调了半个多小时)
                Inc(f[x][v],res);//统计答案
            }
            vis[x][v]=0;//将访问标记删除
            if(x==n&&!v) ++f[x][v];//特判边界条件
            return f[x][v];//返回
        }
}DFS;
int main()
{
    register int i,T,x,y,z,res,ans;F.read(T);
    while(T--)
    {
    	for(F.read(n),F.read(m),F.read(k),F.read(MOD),i=ee=rev_ee=ans=0;i<=n;++i) lnk[i]=rev_lnk[i]=0;//清空数组,因为有多组数据
    	for(i=1;i<=m;++i) F.read(x),F.read(y),F.read(z),add(x,y,z),rev_add(y,x,z);//读入
    	for(i=0,SPFA.Rev_GetAns(n),DFS.Clear();i<=k;++i)//枚举多走的路径长度
        {
            if(!~(res=DFS.GetAns(1,i))) {ans=-1;break;}//如果无数解,输出-1
            Inc(ans,res);//统计答案
        } 
        F.write(ans),F.write_char('\n');//输出答案
    }
    return F.end(),0;
}
posted @ 2018-10-28 18:13  TheLostWeak  阅读(130)  评论(0编辑  收藏  举报