\(Des\)

给定一个有向图,起点为\(1\),终点为\(n\),求和最短路相差不超过\(k\)的路径数量.有\(0\)边.如果有无数条,则输出\(-1\).

\(n\leq 10^5,k\leq 50\)

\(Sol\)

首先,有无数条边的情况一定是在与最短路相差不超过\(k\)的一条路上有\(0\)环.

先不考虑\(0\)边和\(0\)环,\(get\ 70pts\)做法:先跑一个最短路,\(dis[i]\)表示从\(1\)到\(i\)的最短路径.记\(f[u][k]\)表示从\(1\)到\(u\)路径长度不超过\(dis[u]+k\)的路径条数,\(f[u][k]\)显然是转移到\(f[v][dis[u]+k+w(u,v)-dis[v]]\).转移顺序显然是从\(dis\)小的转移到\(dis\)大的,直接按\(dis\)从小到大的顺序.于是一个\(O(NK)\)的\(dp\)就出炉了.

考虑正解.

首先看怎么判断无穷解.记\(f_i\)为\(1\)到\(i\)的最短路,\(g_i\)为\(i\)到\(n\)的最短路.判断无穷解:先把所有可行的\(0\)边拎出来,再看它们是不是组成了环.一条边\((i,j)\)可行当且仅当\(f_i+g_j+w\leq f_n+k\).于是这里就可以简单判断了.

接下来仍然是\(dp\),但是因为\(0\)边的存在所以并不能直接按\(dis\)从小到大的顺序转移,我们需要另外一种方法来确定\(0\)边两个端点的转移顺序.考虑把所有可行的\(0\)边的两个端点拎出来,然后拓扑排序.最后对于所有的点,只需要按照\(f\)为第一关键字,拓扑排序排出来的顺序为第二关键字排序,按照这个顺序更新即可.

其实还有更加简单的记忆化搜索的写法:

\(dfs(u,res)\)表示已经从\(1\)号结点跑到\(u\)结点,还有可以比最短路多走\(res\)的长度,走到\(n\)结点的方案数.显然这里需要跑一个反向最短路\(dis\).答案是\(\sum dfs(v,res-(dis_v+w-dis_u))\).记忆化一下即可.

怎么判断\(0\)环呢?只需要记一个\(stk_{u,res}\)表示在搜索树中当前结点的祖先结点们有没有出现过这个状态,如果有,那么就一定有\(0\)环.

我永远喜欢记搜!

对了,还看到一个非常神仙的分层图求法.在这里.

\(Code\)

#include<bits/stdc++.h>
#define il inline
#define Ri register int
#define go(i,a,b) for(Ri i=a;i<=b;++i)
#define yes(i,a,b) for(Ri i=a;i>=b;--i)
#define e(i,u) for(Ri i=b[u];i;i=a[i].nt)
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define db double
#define inf 2147480000
#define pr pair<int,int>
#define mp make_pair
#define fi first
#define sc second
using namespace std;
il int read()
{
Ri x=0,y=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*y;
}
const int N=1e5+5;
int n,m,k,p,b[N],ct,dis[N],as,rem[N][55];
bool fl,stk[N][55];
priority_queue<pr>q;
struct nd{int v,w,nt;}a[N*2];
struct ed{int u,v,w;}e[N*2];
il void add(Ri u,Ri v,Ri w){a[++ct]=(nd){v,w,b[u]};b[u]=ct;}
il void inc(Ri &x,Ri y){x+=y;if(x>=p)x-=p;}
il void dijkstra()
{
go(i,1,n-1)dis[i]=inf;
dis[n]=0;q.push(mp(0,n));
while(q.size())
{
Ri u=q.top().sc,d=-q.top().fi;q.pop();
if(d>dis[u])continue;
e(i,u)
{
Ri v=a[i].v,w=a[i].w;
if(d+w<dis[v]){dis[v]=d+w;q.push(mp(-dis[v],v));}
}
}
}
il int dfs(Ri u,Ri res)
{
if(stk[u][res]){fl=1;return 0;}
if(rem[u][res])return rem[u][res];
stk[u][res]=1;Ri ret=0;
e(i,u)
{
Ri v=a[i].v,w=a[i].w,tmp=res-(dis[v]+w-dis[u]);
if(tmp>=0)inc(ret,dfs(v,tmp));
if(fl)return 0;
}
stk[u][res]=0;return rem[u][res]=ret;
}
int main()
{
Ri T=read();
while(T--)
{
n=read(),m=read(),k=read(),p=read(),as=0,fl=0;
go(i,1,m)e[i]=(ed){read(),read(),read()};
mem(b,0);ct=0;
go(i,1,m)add(e[i].v,e[i].u,e[i].w);
dijkstra();
mem(b,0);ct=0;
go(i,1,m)add(e[i].u,e[i].v,e[i].w);
mem(stk,0);mem(rem,0);rem[n][0]=1;
go(i,0,k){inc(as,dfs(1,i));if(fl)break;}
if(fl)printf("-1\n");
else printf("%d\n",as);
}
return 0;
}

随机推荐

  1. 2016国产开源软件Top100(Q1)

    2016国产开源软件Top100(Q1) 随着互联网的发展.开放标准的普及和虚拟化技术的应用等诸多IT新领域的创新及拓展,开源技术凭借其开放性.低成本.稳定性.灵活性.安全性和技术创新性等特点迅速走向 ...

  2. 2019-9-2-win10-uwp-应用转后台清理内存

    title author date CreateTime categories win10 uwp 应用转后台清理内存 lindexi 2019-09-02 12:57:38 +0800 2018-2 ...

  3. 解决电脑性能一般,打开webstorm后,电脑比较卡的问题

    刚到一公司实习,要求使用webstrom开发前端,但安装后发现自己的电脑很卡,特别是在运行项目时,卡的不要不要的. 后来,发现一奇淫技巧,用sublime代替webstrom,但是没法启动项目啊 找到 ...

  4. python中使用指定GPU

    import os os.environ["CUDA_VISIBLE_DEVICES"] = "2" # 或 os.environ["CUDA_VIS ...

  5. hdu 2639 Bone Collector II(01背包 第K大价值)

    Bone Collector II Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others ...

  6. POJ 2251宽搜、

    因为这个题做了两次犯了两次不同的错误. 第一次用的dfs死活都超时 第二次把定义队列定义在了全局变量的位置,导致连WA了几次.最后找到原因的我真的想一巴掌拍死自己 #include<cstdio ...

  7. JVM基础--JVM参数之堆栈空间配置

    目录 堆配置 年轻代 Eden区 永久代(JDK1.7) 元空间(JDK1.8) 栈空间 直接内存 总结 参考资料 JVM系列目录 JVM 中最重要的一部分就是堆空间了,基本上大多数的线上 JVM 问 ...

  8. java Math和Random和UUID

    Math类 public final class Math extends Object 以下X表示double,float,int, long abs(X x):求绝对值 max(X x1,X x2 ...

  9. linux清理函数

    每个非试验性的模块也要求有一个清理函数, 它注销接口, 在模块被去除之前返回所有资 源给系统. 这个函数定义为: static void   exit cleanup_function(void) { ...

  10. Spring Data JPA坑点记录

    本篇进行Spring-data-jpa的介绍,几乎涵盖该框架的所有方面,在日常的开发当中,基本上能满足所有需求.这里不讲解JPA和Spring-data-jpa单独使用,所有的内容都是在和Spring ...