[NOIP2017]逛公园

题意:求 dis(1,n)<=MinDis(1,n)+K d i s ( 1 , n ) <= M i n D i s ( 1 , n ) + K 的路径数

算法一:DP

首先你可以考虑到 Day1 D a y 1 DP D P 去哪里了?

没错,你只要再注意一下 K50 K ≤ 50 就大概能想到这是一个与 k k 有关的 DP D P

①:考虑 30pts:K=0 30 p t s : K = 0

右转LuoguP1608路径统计(P1144最短路计数可以顺带A掉)

②:考虑 70pts 70 p t s :没有 0 0

dis1u d i s 1 u 表示1到u的最短路, disnu d i s n u 表示 u u n n 的最短路(这个可以建反图跑出来)

考虑 f[u][j] f [ u ] [ j ] 表示 dis(1,u)dis1u+j d i s ( 1 , u ) ≤ d i s 1 u + j 的路径数

那么对于 edge(u,v,w) e d g e ( u , v , w )

那么从 1>u>v 1 − > u − > v 这条路径的长度就是 dis1u+j+wdis1v d i s 1 u + j + w − d i s 1 v

如果 dis1u+wdis1v+jK d i s 1 u + w − d i s 1 v + j ≤ K

就可以从 f[u][j] f [ u ] [ j ] 转移到 f[v][dis1u+j+wdis1v] f [ v ] [ d i s 1 u + j + w − d i s 1 v ]

所以直接先从 1 1 跑最短路然后直接 O(KM)DP O ( K M ) D P ok o k

当然这样还是有问题的

因为我们必须要先更新 dis1 d i s 1 小的点

所以要先按照 dis1 d i s 1 排个序再去转移就有 70pts 70 p t s

③:考虑 100pts: 100 p t s :

对于有 0 0 边的图,显然直接按照 dis1 d i s 1 去排序是不行的

例如 a>b>c,w=0 a − > b − > c , w = 0

因为他的一个有向图,更新顺序显然是 a,b,c a , b , c

所以考虑拓扑排序来确定 0 0 边两个端点的更新顺序

即对于 0 0 边,把其加入新图,然后对于新图拓扑排序确定” 0 0 点”的更新顺序

然后对于 1 − 1 的情况显然是对于一条满足条件的路径上有一个 0 0

拓扑排序完了且入度不等于 0 0 说明这个点在 0 0 环上

同一个 0 0 环上任意一个点到 1 1 的最短路和到 n n 的最短路都一样

所以当这个点 i i 满足 dis1i+disni<=dis1n+K d i s 1 i + d i s n i <= d i s 1 n + K 时,就可以输出 1 − 1

然后最后以 dis1 d i s 1 disn d i s n 为第一关键字,拓扑序为第二关键字排序再转移就 ok o k

这里是考场代码…最短路是用线段树跑的(线段树我目前我知道最快的,比斐波那契堆和配对堆什么的快多了,不信可以试试这道题)

#include
#include
#include
#include
#define re register int
#define fp(i,a,b) for(re i=a,I=b;i<=I;++i)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
using namespace std;
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sdf(T&x){
    char c;T y=1;while(c=gc(),(c<48||571)if(c=='-')y=-1;x=c^48;
    while(c=gc(),4758)x=(x<<1)+(x<<3)+(c^48);x*=y;
}
using namespace std;
const int N=1e5+5,M=2e5+5;
typedef int arr[N];
struct eg{int nx,to,w;}e[M<<1],E[M];
int T,n,m,K,P,ce=1,Ce,ans,*dis,f[N][55];arr dis1,disn,fi1,fin,vis,in,q,Fi,id,da;
namespace seg{
    int tr[N<<2],sgt;
    inline void Set(re n){sgt=1;while(sgt<=n)sgt<<=1;--sgt;tr[0]=N-1;}
    inline void clr(){fp(i,1,(sgt<<1)+1)tr[i]=0;}
    inline int cmp(const re&a,const re&b){return dis[a]inline void mdy(re x,re w){for(re i=x+sgt;dis[tr[i]]>w;i>>=1)tr[i]=x;dis[x]=w;}
    inline void del(re x){tr[x+=sgt]=0;x>>=1;while(x)tr[x]=cmp(tr[x<<1],tr[x<<1|1]),x>>=1;}
}
using namespace seg;
void dij(re s,re*Dis,re*fi){
    dis=Dis;memset(dis,9,4*(n+1));clr();mdy(s,0);
    fp(T,1,n){
        re u=tr[1];del(u);
        for(re i=fi[u],v;i;i=e[i].nx)
            if(dis[v=e[i].to]>dis[u]+e[i].w)
                mdy(v,dis[u]+e[i].w);
    }
}
inline void add(re u,re v,re w,re*fi){e[++ce]=(eg){fi[u],v,w};fi[u]=ce;}
inline void ADD(re u,re v){E[++Ce]=(eg){Fi[u],v,0};Fi[u]=Ce;}
inline bool comp(const re&a,const re&b){return dis1[a]==dis1[b]?id[a]bool topsort(){
    re h=1,t=0;
    fp(i,1,n)if(!in[i])q[++t]=i;
    while(h<=t){
        re u=q[h++];
        for(re i=Fi[u],v;i;i=E[i].nx)
            if(!--in[v=E[i].to])q[++t]=v;
    }
    fp(i,1,n)id[q[i]]=i;
    fp(i,1,n)if(in[i]&&dis1[i]+disn[i]<=K+dis1[n])return 1;
    return 0;
}
inline void mod(re&a){a>=P?a-=P:0;}
int main(){
    #ifndef ONLINE_JUDGE
        file("park");
    #endif
    sdf(T);
    while(T--){
        memset(fi1,0,sizeof fi1);memset(fin,0,sizeof fin);
        memset(Fi,0,sizeof Fi);ce=Ce=ans=0;
        sdf(n);sdf(m);sdf(K);sdf(P);re u,v,w;Set(n);
        while(m--){
            sdf(u),sdf(v),sdf(w),add(u,v,w,fi1),add(v,u,w,fin);
            if(!w)ADD(u,v),++in[v];
        }
        dij(1,dis1,fi1);dij(n,disn,fin);;
        if(topsort()){puts("-1");continue;}
        memset(f,0,sizeof f);f[1][0]=1;
        fp(i,1,n)da[i]=i;sort(da+1,da+n+1,comp);
        fp(k,0,K)fp(p,1,n)
            for(re u=da[p],d=dis1[u],i=fi1[u];i;i=e[i].nx)
                if(e[i].w-dis1[e[i].to]+d+k<=K)
                    mod(f[e[i].to][e[i].w-dis1[e[i].to]+d+k]+=f[u][k]);
        fp(k,0,K)mod(ans+=f[n][k]);
        printf("%d\n",ans);
    }
return 0;
}

上面这个做法常数还不小,其实想想就知道,而且写起来很长

然后还有一种更加优美的做法就是记忆化搜索

算法二:记忆化搜索

只要跑一次反向的最短路

f[u][k] f [ u ] [ k ] 表示 dis(u,n)<=MinDis(u,n)+k d i s ( u , n ) <= M i n D i s ( u , n ) + k 的方案数,答案就是 f[1][K] f [ 1 ] [ K ]

考虑 egde(u,v,w) e g d e ( u , v , w )

同样的道理走这条边的话, dis(v,n)=MinDis(v,n)+wMinDis(u,n) d i s ( v , n ) = M i n D i s ( v , n ) + w − M i n D i s ( u , n )

f[u][k]=f[v][k(MinDis(v,n)MinDis(u,n)+w)] ⇒ f [ u ] [ k ] = ∑ f [ v ] [ k − ( M i n D i s ( v , n ) − M i n D i s ( u , n ) + w ) ]

这样怎么判 0 0 环呢?只要在搜索的时候记录个 instack i n s t a c k ok o k

如果当前的 v v 还在搜索的栈中就可以直接返回 1 − 1

#include
#include
#define re register int
#define fp(i,a,b) for(re i=a,I=b;i<=I;++i)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sdf(T&x){
    char c;T y=1;while(c=gc(),(c<48||571)if(c=='-')y=-1;x=c^48;
    while(c=gc(),4758)x=(x<<1)+(x<<3)+(c^48);x*=y;
}
const int N=1e5+5,M=2e5+5;
typedef int arr[N];
struct eg{int nx,to,w;}e[M<<1];
int T,n,m,K,P,ce,f[N][51];arr dis,f1,fn;bool in[N][51];
namespace seg{
    int tr[N<<2],sgt;
    inline void Set(re n){sgt=1;while(sgt<=n)sgt<<=1;--sgt;tr[0]=N-1;}
    inline void clr(){fp(i,1,(sgt<<1)+1)tr[i]=0;}
    inline int cmp(const re&a,const re&b){return dis[a]inline void mdy(re x,re w){for(re i=x+sgt;dis[tr[i]]>w;i>>=1)tr[i]=x;dis[x]=w;}
    inline void del(re x){tr[x+=sgt]=0;x>>=1;while(x)tr[x]=cmp(tr[x<<1],tr[x<<1|1]),x>>=1;}
}
using namespace seg;
void dij(){
    memset(dis,9,4*(n+1));clr();mdy(n,0);
    fp(T,1,n){
        re u=tr[1];del(u);
        for(re i=fn[u],v;i;i=e[i].nx)
            if(dis[v=e[i].to]>dis[u]+e[i].w)
                mdy(v,dis[u]+e[i].w);
    }
}
inline void add(re u,re v,re w,re*fi){e[++ce]=(eg){fi[u],v,w};fi[u]=ce;}
inline void mod(re&a){a>=P?a-=P:0;}
int dfs(re u,re k){
    if(in[u][k])return -1;
    if(f[u][k])return f[u][k];
    in[u][k]=1;u==n?f[u][k]=1:0;
    for(re i=f1[u],v,w,tp;i;i=e[i].nx)
        if((tp=dis[v=e[i].to]-dis[u]+e[i].w)<=k){
            if((w=dfs(v,k-tp))==-1)return f[u][k]=-1;
            mod(f[u][k]+=w);
        }
    return in[u][k]=0,f[u][k];
}
int main(){
    #ifndef ONLINE_JUDGE
        file("park");
    #endif
    sdf(T);
    while(T--){
        memset(f,0,sizeof f);memset(in,0,sizeof in);
        sdf(n);sdf(m);sdf(K);sdf(P);Set(n);re u,v,w;
        memset(f1,ce=0,4*(n+1));memset(fn,0,4*(n+1));
        while(m--)sdf(u),sdf(v),sdf(w),add(u,v,w,f1),add(v,u,w,fn);
        dij();printf("%d\n",dfs(1,K));
    }
return 0;
}

你可能感兴趣的:(DP,记忆化搜索,最短路)