NOIP2017提高组Day1T3 逛公园 洛谷P3953 Tarjan 强连通缩点 SPFA 动态规划 最短路 拓扑序
原文链接https://www.cnblogs.com/zhouzhendong/p/9258043.html
题目传送门 - 洛谷P3953
题目传送门 - Vijos P2030
题意
给定一个有向图,有 $n$ 个节点 $m$ 条边,边权值 $\in[0,1000]$ 。
小明要从 $1$ 走到 $n$ ,要求路径长度最大为 $d+k$ ,其中 $d$ 为 $1$ 到 $n$ 最短路长度。
问小明有多少种走法,答案对 $p$ 取模。如果有无数种走法,那么输出 $-1$ 。
$n\leq 100000,m\leq 200000,0\leq k\leq 50,1\leq p \leq 10^9$
题解
我们首先看看没有 $0$ 边,即答案不为 $-1$ 的情况。
显然我们可以先 SPFA 跑一遍最短路。
我们记 $dis_i$ 表示到节点 $i$ 的最短路长度。
令 $dp_{i,j}$ 表示到达第 $i$ 个节点,路径长度为 $dis_i+j$ 的路径个数。
显然可以在最短路网上生成的最短路 DAG 上搞一个拓扑序,然后 $\Theta(nk)$ DP 解决。
那么如果出现了 $0$ 环呢?
首先我们不能草率的定下“图中有 $0$ 环答案就是 $-1$”这种结论。
当然,“ $1$ 至 $n$ 通路上有零环答案是 $-1$” 也是错的。
考虑到 $0$ 环上面可以随意转移,所以同一个 $0$ 环上的节点就像一个节点一样。
所以我们可以 Tarjan 缩点一波。具体地:如果边权为 $0$ ,那么视为有边,否则视为无边。强连通缩点建立新图。
于是我们在新环上面做 DP 。
考虑在 DP 的过程中,一旦遇到零环上的点 $i$ ,一旦 $dp_{i,j}>0$ ,那么 $dp_{i,j}=\infty$ 。
并用此更新后面的点。
这里需要注意一个细节:由于我们计算的 $dp_{i,j}$ 是模意义下的,当 $dp_{i,j}\equiv 0\pmod p$ 时,不一定满足 $dp_{i,j}=0$,所以我们需要再开一个数组来记录 $dp_{i,j}$ 的特性:是 $0$ 、有限正整数 还是 $\infty$ 。
然后我 TLE 了……蒟蒻自带大常数啊 QAQ ,只能卡常了。
搞了好久之后,才想到好的做法。由于数组模拟链表跳着访问数组会很慢的,所以:
可以通过把要访问的边按照顺序放在连续的存储单元内,并顺序访问来提高速度。
结果卡了两倍多的常数,过掉了。
代码
#include <bits/stdc++.h> using namespace std; const int N=100005,M=N*2,K=55; int T,n,m,k,p; int read(){ int x=0; char ch=getchar(); while (!('0'<=ch&&ch<='9')) ch=getchar(); while ('0'<=ch&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar(); return x; } struct Gragh{ int cnt,x[M],y[M],z[M],nxt[M],fst[N]; void clear(){ cnt=0; memset(fst,0,sizeof fst); } void add(int a,int b,int c){ y[++cnt]=b,x[cnt]=a,z[cnt]=c,nxt[cnt]=fst[a],fst[a]=cnt; } }g,g2; int Time,top,tot,bh[N],dfn[N],low[N],vis[N],st[N],inst[N],Nodecnt[N]; int dp[N][K],type[N][K]; int X[M],Y[M],Z[M],e; void Tarjan(int x){ vis[x]=1,dfn[x]=low[x]=++Time; st[++top]=x,inst[x]=1; for (int i=g.fst[x];i;i=g.nxt[i]){ if (g.z[i]>0) continue; int y=g.y[i]; if (!vis[y]){ Tarjan(y); low[x]=min(low[x],low[y]); } else if (inst[y]) low[x]=min(low[x],low[y]); } if (dfn[x]==low[x]){ tot++; bh[st[top]]=tot; inst[st[top]]=0; while (st[top--]!=x){ bh[st[top]]=tot; inst[st[top]]=0; } } } int q[N],head,tail,qmod,dis[N],f[N],in[N]; void SPFA(int S){ memset(f,0,sizeof f); for (int i=0;i<N;i++) dis[i]=1.5e9; head=tail=0,qmod=tot+2; dis[S]=0,f[S]=1,q[++tail]=S; while (head!=tail){ int x=q[head=(head+1)%qmod],y; f[x]=0; for (int i=g2.fst[x];i;i=g2.nxt[i]) if (dis[y=g2.y[i]]>dis[x]+g2.z[i]){ dis[y]=dis[x]+g2.z[i]; if (!f[y]){ f[y]=1; q[tail=(tail+1)%qmod]=y; } } } } void solve(){ n=read(),m=read(),k=read(),p=read(); int S=1,T=n; g.clear(); for (int i=1;i<=m;i++){ int a=read(),b=read(),c=read(); g.add(a,b,c); } Time=top=tot=0; memset(bh,0,sizeof bh); memset(st,0,sizeof st); memset(dfn,0,sizeof dfn); memset(low,0,sizeof low); memset(vis,0,sizeof vis); memset(inst,0,sizeof inst); for (int i=1;i<=n;i++) if (!vis[i]) Tarjan(i); g2.clear(); for (int i=1;i<=g.cnt;i++) if (bh[g.x[i]]!=bh[g.y[i]]) g2.add(bh[g.x[i]],bh[g.y[i]],g.z[i]); memset(Nodecnt,0,sizeof Nodecnt); for (int i=1;i<=n;i++) Nodecnt[bh[i]]++; S=bh[S],T=bh[T]; SPFA(S); memset(in,0,sizeof in); for (int i=1;i<=g2.cnt;i++) if (dis[g2.x[i]]+g2.z[i]==dis[g2.y[i]]) in[g2.y[i]]++; head=tail=0; for (int i=1;i<=tot;i++) if (in[i]==0) q[++tail]=i; while (head<tail){ int x=q[++head],y; for (int i=g2.fst[x];i;i=g2.nxt[i]) if (dis[x]+g2.z[i]==dis[g2.y[i]]){ in[g2.y[i]]--; if (in[g2.y[i]]==0) q[++tail]=g2.y[i]; } } memset(type,0,sizeof type); memset(dp,0,sizeof dp); dp[S][0]=type[S][0]=1; int ans=0; e=0; for (int iq=1;iq<=tot;iq++) for (int i=g2.fst[q[iq]];i;i=g2.nxt[i]){ e++; X[e]=g2.x[i]; Y[e]=g2.y[i]; Z[e]=g2.z[i]; } for (int ix=0;ix<=k;ix++){ int x,y,z; for (int i=1;i<=tot;i++) if (Nodecnt[i]>1&&type[i][ix]==1) type[i][ix]=2; for (int i=1;i<=e;i++){ x=X[i],y=Y[i],z=Z[i]; if (type[x][ix]==0) continue; int iy=dis[x]+ix+z-dis[y]; if (iy>k||type[y][iy]==2) continue; type[y][iy]=type[x][ix]; if (type[x][ix]==1){ dp[y][iy]=dp[y][iy]+dp[x][ix]; if (dp[y][iy]>=p) dp[y][iy]-=p; } } if (type[T][ix]==2){ ans=-1; break; } else if (type[T][ix]==1) ans=(ans+dp[T][ix])%p; } printf("%d\n",ans); } int main(){ T=read(); while (T--) solve(); return 0; }