noip2017 逛公园
https://www.luogu.com.cn/problem/P3953
k=0:
直接在spfa过程中最短路计数。
没有0边:
定义路径长度的增量为它比最短路多的距离
dp[i][j]表示从1到i,路径长度增量为j的路径条数
枚举一条从u->v,距离为w的边
新的增量为dis(1,u)+w-dis(1,v)+j
即dp[v][ dis(1,u)+w-dis(1,v)+j ]+=dp[u][j]
如果新的增量不等于原来的增量,那么从0到k枚举增量,转移不用考虑顺序
但是如果走的边不会产生新的增量,点与点之间的转移存在先后顺序,可以将所有点按1号点到它的最短路从小到大排序
有0边:
先不考虑无穷的情况
0边带来的影响是,按最短路排序无法明确0边起点终点的先后顺序
因为0边的起点肯定要在终点的前面,才能让0边起点的贡献通过0边终点转移到其他点
既然如此可以拓扑排序
那什么时候有无穷多组解呢?
直观的情况是有一个不会产生新增量的环,在环上无限转圈
那么将会经过的且不会产生新增量的边构建新图,判断新图上有没有环即可
即将满足以下2个要求的边(从u到v,长度为w)构建新的图:
1、dis(1,u)+w+dis(v,n)<=dis(1,n)+k (该边会经过)
2、dis(1,u)+w=dis(1,v) (从1走过来,该边不会产生新的增量)
判环也是拓扑排序即可
在新图上dp的过程中
先进行不会产生新增量的转移,即dp[u][j]向dp[v][j]的转移,转移顺序按拓扑序
再进行会产生新增量的转移,即dp[u][j]向dp[v][ dis(1,u)+w-dis(1,v)+j ],因为产生了新的增量,所以顺序随便
#include<queue> #include<cstdio> #include<cstring> using namespace std; #define N 100001 int T,n,m,k,p; int eu[N<<1],ev[N<<1],ew[N<<1]; int front[N],to[N<<2],nxt[N<<2],val[N<<2],tot,front2[N]; queue<int>q; int dis[N],dis2[N]; bool vis[N]; int in[N]; int st[N],id[N]; int dp[N][51]; int nn; bool use[N]; void add(int u,int v,int w,int *head) { to[++tot]=v; nxt[tot]=head[u]; head[u]=tot; val[tot]=w; } void spfa(int *head,int start,int *d) { for(int i=1;i<=n;++i) d[i]=1e9; d[start]=0; q.push(start); vis[start]=true; int now,t; while(!q.empty()) { now=q.front(); q.pop(); vis[now]=false; for(int i=head[now];i;i=nxt[i]) { t=to[i]; if(d[t]>d[now]+val[i]) { d[t]=d[now]+val[i]; if(!vis[t]) { q.push(t); vis[t]=true; } } } } } void rebuild() { memset(front,0,sizeof(front)); memset(in,0,sizeof(in)); memset(use,false,sizeof(use)); tot=0; for(int i=1;i<=m;++i) { if(dis[eu[i]]+dis2[ev[i]]+ew[i]<=dis[n]+k && dis[eu[i]]+ew[i]==dis[ev[i]]) { add(eu[i],ev[i],0,front); in[ev[i]]++; use[eu[i]]=true; use[ev[i]]=true; } } nn=0; for(int i=1;i<=n;++i) if(use[i]) ++nn; for(int i=1;i<=m;++i) ew[i]=dis[eu[i]]+ew[i]-dis[ev[i]]; } bool topsort() { int now,top,dn=0; st[top=1]=1; id[dn=1]=1; while(top) { now=st[top--]; for(int i=front[now];i;i=nxt[i]) { in[to[i]]--; if(!in[to[i]]) { id[++dn]=to[i]; st[++top]=to[i]; } } } for(int i=1;i<=n;++i) if(in[i]) return false; return true; } void solve() { memset(dp,0,sizeof(dp)); dp[1][0]=1; int now,ans=0; for(int i=0;i<=k;++i) { for(int j=1;j<=nn;++j) { now=id[j]; for(int l=front[now];l;l=nxt[l]) dp[to[l]][i]=(dp[to[l]][i]+dp[now][i])%p; } for(int j=1;j<=m;++j) { if(i+ew[j]<=k && ew[j]>0) dp[ev[j]][i+ew[j]]=(dp[ev[j]][i+ew[j]]+dp[eu[j]][i])%p; } ans=(ans+dp[n][i])%p; } printf("%d\n",ans); } int main() { scanf("%d",&T); while(T--) { scanf("%d%d%d%d",&n,&m,&k,&p); tot=0; memset(front,0,sizeof(front)); for(int i=1;i<=m;++i) { scanf("%d%d%d",&eu[i],&ev[i],&ew[i]); add(eu[i],ev[i],ew[i],front); } spfa(front,1,dis); memset(front2,0,sizeof(front2)); for(int i=1;i<=m;++i) { add(ev[i],eu[i],ew[i],front2); } spfa(front2,n,dis2); rebuild(); if(!topsort()) { printf("-1\n"); continue; } solve(); } return 0; } /* 1 6 7 3 100 1 2 0 2 3 0 3 4 0 4 5 1 5 2 0 2 5 0 5 6 0 */