【NOIP2017】洛谷3953 逛公园题解(最短路+记忆化搜索+拓扑排序)

题目:luogu3953.
题目大意:给定一张 n n n个点 m m m条边的非负整数权图,求解这张图中比 1 1 1 n n n最短路长不超过 k k k 1 1 1 n n n的路径条数,若无解输出 − 1 -1 1.
1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 2 ∗ 1 0 5 , 1 ≤ k ≤ 50 1\leq n\leq 10^5,1\leq m\leq 2*10^5,1\leq k\leq 50 1n105,1m2105,1k50.

很显然这种最短路计数的题,当然是要先跑一遍最短路了,设点 1 1 1到点 i i i的最短路为 d i s [ i ] dis[i] dis[i].

考虑70pts的部分分,也就是先不考虑无穷条路径的情况.那可以直接设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示所有从 1 1 1 i i i的路径中,比 1 1 1 i i i的最短路长 j j j的路径条数.转移时若有边 ( x , y , v ) (x,y,v) (x,y,v),则状态 d p [ y ] [ i ] dp[y][i] dp[y][i]就可以通过 d p [ x ] [ i + d i s [ y ] − v − d i s [ x ] ] dp[x][i+dis[y]-v-dis[x]] dp[x][i+dis[y]vdis[x]]转移,记忆化搜索实现即可.时间复杂度 O ( ( n + m ) k ) O((n+m)k) O((n+m)k).

然后是无穷条路径的情况.无穷条路径的情况需要满足两个条件,一个是有 0 0 0环,一个是有一条长度不超过最短路长度 + k +k +k的路径经过 0 0 0环,问题变为了如何判断这两个条件是否满足.

先用拓扑排序把所有 0 0 0环上的点找出来,判断是否有 0 0 0环上的点被一条满足条件的路径经过,判断某个点 i i i是否被经过只需要判断 1 1 1 i i i的最短路加上 i i i n n n的最短路是否小于等于 1 1 1 n n n的最短路 + k +k +k即可.

总时间复杂度 O ( ( n + m ) k ) O((n+m)k) O((n+m)k).

代码如下:

#include<bits/stdc++.h>
using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=100000,M=200000,K=50,INF=(1<<30)-1;

int n,m,sk,mod;

int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
int mul(int a,int b){return (LL)a*b%mod;}
void sadd(int &a,int b){a=add(a,b);}
void ssub(int &a,int b){a=sub(a,b);}
void smul(int &a,int b){a=mul(a,b);}

struct side{
  int y,next,v;
}e[M*2+9];
int lin[2][N+9],cs;

void Ins(int id,int x,int y,int v){e[++cs].y=y;e[cs].v=v;e[cs].next=lin[id][x];lin[id][x]=cs;}

struct state{
  int x,v;
  state(int X=0,int V=0){x=X;v=V;}
  bool operator > (const state &p)const{return v>p.v;}
};
priority_queue<state,vector<state>,greater<state> >qmin;
int dis[2][N+9],vis[N+9];

void Dijkstra(int id,int st){
  for (int i=1;i<=n;++i) dis[id][i]=INF,vis[i]=0;
  dis[id][st]=0;qmin.push(state(st,0));
  while (!qmin.empty()){
  	int t=qmin.top().x;qmin.pop();
  	if (vis[t]) continue;
  	vis[t]=1;
  	for (int i=lin[id][t];i;i=e[i].next)
  	  if (dis[id][t]+e[i].v<dis[id][e[i].y])
  	    qmin.push(state(e[i].y,dis[id][e[i].y]=dis[id][t]+e[i].v));
  }
}

int deg[N+9];
queue<int>q;

void Topsort(){
  for (int i=1;i<=n;++i) deg[i]=0,vis[i]=1;
  for (int i=1;i<=n;++i)
    for (int j=lin[0][i];j;j=e[j].next)
      if (!e[j].v) ++deg[e[j].y];
  for (int i=1;i<=n;++i)
    if (!deg[i]) q.push(i);
  while (!q.empty()){
  	int t=q.front();q.pop();
  	vis[t]=0;
  	for (int i=lin[0][t];i;i=e[i].next)
  	  if (!e[i].v&&!(--deg[e[i].y])) q.push(e[i].y);
  }
}

int dp[N+9][K+9];

int Dfs_dp(int k,int v){
  if (v<0||v>sk) return 0;
  if (dp[k][v]^-1) return dp[k][v];
  dp[k][v]=0;
  for (int i=lin[1][k];i;i=e[i].next)
    sadd(dp[k][v],Dfs_dp(e[i].y,v+dis[0][k]-e[i].v-dis[0][e[i].y]));
  return dp[k][v];
}

int ans;

Abigail into(){
  scanf("%d%d%d%d",&n,&m,&sk,&mod);
  for (int i=1;i<=n;++i) lin[0][i]=lin[1][i]=0;
  cs=0;
  for (int i=1;i<=m;++i){
  	int x,y,v;
  	scanf("%d%d%d",&x,&y,&v);
  	Ins(0,x,y,v);Ins(1,y,x,v);
  }
}

Abigail work(){
  Dijkstra(0,1);
  Dijkstra(1,n);
  for (int i=1;i<=n;++i)
    vis[i]=dis[0][i]+dis[1][i]-dis[0][n]<=sk;
  Topsort();
  for (int i=1;i<=n;++i)
    if (vis[i]&&dis[0][i]+dis[1][i]<=dis[0][n]+sk) {ans=-1;return;}
  for (int i=1;i<=n;++i)
    for (int j=0;j<=sk;++j) dp[i][j]=-1;
  dp[1][0]=1;
  ans=0;
  for (int i=0;i<=sk;++i)
    sadd(ans,Dfs_dp(n,i));
}

Abigail outo(){
  printf("%d\n",ans);
} 

int main(){
  int T;
  scanf("%d",&T);
  while (T--){
    into();
    work();
    outo();
  }
  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值