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;
    }
    
  • 相关阅读:
    一张图片入门Python
    4.1. 如何在Windows环境下开发Python
    你必须知道的EF知识和经验
    XUnit的使用
    如何使用NUnit
    Entity Framework 不支持DefaultValue
    Have You Ever Wondered About the Difference Between NOT NULL and DEFAULT?
    Validation failed for one or more entities. See 'EntityValidationErrors' property for more details
    Entity Framework 与多线程
    sqlite中的自增主键
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu3953.html
Copyright © 2011-2022 走看看