zoukankan      html  css  js  c++  java
  • 【洛谷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('
    ');//输出答案
        }
        return F.end(),0;
    }
    
  • 相关阅读:
    ReentrantLock 非公平锁不公平在哪
    spring 初始化Bean
    spring 循环引用问题,在一次问题调试过程中发现有个小伙伴竟然把循环引用设置成false了。估计是百度的时候没小心额外的代码吧。。。
    Maya2019下载安装与激活
    Maya2014下载安装与激活
    Maya2017下载安装与激活
    Windows 好用的护眼软件
    万兴全能格式转换器_11.2.0.232 下载安装和激活
    处理视频相关的软件
    几款好用的录屏软件推荐
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu3953.html
Copyright © 2011-2022 走看看