[noip2017 D1T3]逛公园(拆点最短路+拓扑数最短路个数)

题目描述

策策同学特别喜欢逛公园。公园可以看成一张 N N 个点M条边构成的有向图,且没有 自环和重边。其中1号点是公园的入口, N N 号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。

策策每天都会去逛公园,他总是从1号点进去,从N号点出来。

策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果1号点 到 N N 号点的最短路长为d,那么策策只会喜欢长度不超过 d+K d + K 的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?
为避免输出过大,答案对 P P 取模。
如果有无穷多条合法的路线,请输出−1。

输入格式:

第一行包含一个整数 T, 代表数据组数。
接下来 T T 组数据,对于每组数据: 第一行包含四个整数 N,M,K,P,每两个整数之间用一个空格隔开。

接下来 M M 行,每行三个整数ai,bi,ci
代表编号为 ai,bi a i , b i 的点之间有一条权值为 ci c i 的有向边,每两个整数之间用一个空格隔开。

输出格式:

输出文件包含 T T 行,每行一个整数代表答案。
题目




解:

看看 k k 小于50对吧?自然而然想到了拆点。不过怎么拆点就很尴尬了。经过冥思苦想想到一种做法:跑一遍最短路然后把每个点拆成k f[i][j] f [ i ] [ j ] 表示以 i i 为起点当前路径长度为dis[i]+j的状态,里面存从这个点到终点的路径长度,如果没有零权环的话,可以证明这个图是一个dag。

然后就是记忆化搜索找路径条数,这个大家都会。现在你已经得了70分了,然后就是判零权环:注意,不一定图中有零权环就一定无数解,一定要环在路径上才行。我这里直接暴力判环。每个在路径中的点dfs,只走边权为零的边,要是走到自己,则无数解。昨天没有想到,看了别人的题解发现有更简单的判法:求拓扑序(不用dfs写法),如果无法把终点加入拓扑序中则证明路径上有零权环。

代码如下:

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<queue>  
using namespace std;  
struct lxy{  
    int next,to,len;  
}b[200005];  

int head[100005];  
int cnt=0,T,k,m,n,p;  
int f[100005][55];  
bool vis[100005];  
int dis[100005];  
bool ris[100005][55];  
bool col[100005];  
int hhh=0;  
int aim;  

void add(int op,int ed,int len)  
{  
    cnt++;  
    b[cnt].next=head[op];  
    head[op]=cnt;  
    b[cnt].to=ed;  
    b[cnt].len=len;  
}  

inline void Read(int &a) {//梁dalao的读入优化  
    char c=getchar();  
    a=0;  
    while(c>'9'||c<'0')  
        c=getchar();  
    while(c<='9'&&c>='0') {  
        a*=10;  
        a+=c-'0';  
        c=getchar();  
    }  
    return;  
}  

void spfa()//求最短路  
{  
    queue <int> d;  
    dis[1]=0;  
    d.push(1);  
    vis[1]=1;  
    while(!d.empty())  
    {  
        register int noww=d.front();  
        vis[noww]=0;  
        d.pop();  
        for(register int i=head[noww];i!=-1;i=b[i].next)  
        {  
            if(dis[b[i].to]>dis[noww]+b[i].len)  
            {  
                dis[b[i].to]=dis[noww]+b[i].len;  
                if(vis[b[i].to]==0)  
                {  
                    vis[b[i].to]=1;  
                    d.push(b[i].to);  
                }  
            }  
        }  
    }  
}  

int dfs(int u,int r)//数路径条数  
{  
    if(ris[u][r]==1)  
      return f[u][r];  
    ris[u][r]=1;  
    if(u==n)//如果到终点,有一条路到终点  
      f[u][r]=1;  
    register int y;  
    for(register int i=head[u];i!=-1;i=b[i].next)  
    {  
        if(dis[u]+r+b[i].len<=dis[b[i].to]+k)//如果到下个点的路径长不超过下一个点的最短路+k,可以向下走  
        {  
            y=dis[u]+r+b[i].len-dis[b[i].to];  
            f[u][r]=(f[u][r]+dfs(b[i].to,y))%p;  
        }  
    }  
    if(f[u][r]!=0)//如果从这个点可以到终点,标记这个点在路径里  
      col[u]=1;  
    return f[u][r];  
}  

void pan(register int u)//判断零权环  
{  
    if(hhh==1)return;  
    if(vis[u]==1)return;  
    vis[u]=1;  
    for(register int i=head[u];i!=-1;i=b[i].next)  
      if(b[i].len==0)//只走零权边  
      {  
        if(b[i].to==aim)//走到自己答案标记为无穷解  
          hhh=1;  
        else  
         pan(b[i].to);  
      }  
}  

int main()  
{  
    scanf("%d",&T);  
    while(T!=0)  
    {  
        T--;  
        scanf("%d%d%d%d",&n,&m,&k,&p);  
        cnt=0;  
        memset(f,0,sizeof(f));  
        memset(head,-1,sizeof(head));  
        memset(dis,0x7f,sizeof(dis));  
        memset(vis,0,sizeof(vis));  
        memset(ris,0,sizeof(ris));  
        memset(b,0,sizeof(b));  
        memset(col,0,sizeof(col));  
        hhh=0;  
        for(register int i=1;i<=m;i++)  
        {  
            register int x,y,z;  
            Read(x);  
            Read(y);  
            Read(z);  
            add(x,y,z);  
        }  
        spfa();  
        register int ans=dfs(1,0);  
        for(register int i=1;i<=n;i++)//对于每个路径中的点都要判断能否走到自己  
          if(col[i]==1)  
          {  
            memset(vis,0,sizeof(vis));  
            aim=i;  
            pan(i);  
            if(hhh==1)  
              break;  
          }  
        if(hhh==1)  
          printf("-1\n");  
        else  
          printf("%d\n",ans%p);  
    }  
}

复杂度十分玄学,其实有点爆炸,加读入优化勉强卡进2.5秒

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值