洛谷P3953 逛公园 //NOIP2017

48 篇文章 0 订阅
6 篇文章 0 订阅

题目描述

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

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

策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果1号点 到 NN 号点的最短路长为 dd ,那么策策只会喜欢长度不超过 d + Kd+K的路线。

策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?

为避免输出过大,答案对 PP 取模。

如果有无穷多条合法的路线,请输出−1。

输入输出格式

输入格式:

第一行包含一个整数 TT , 代表数据组数。

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

接下来 MM 行,每行三个整数 a_i,b_i,c_iai,bi,ci ,代表编号为 a_i,b_iai,bi 的点之间有一条权值为 c_ici 的有向边,每两个整数之间用一个空格隔开。

输出格式:

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

输入输出样例

输入样例#1:  复制
2
5 7 2 10
1 2 1
2 4 0
4 5 2
2 3 2
3 4 1
3 5 2
1 5 3
2 2 0 10
1 2 0
2 1 0
输出样例#1:  复制
3
-1

说明

【样例解释1】

对于第一组数据,最短路为 3。 1 – 5, 1 – 2 – 4 – 5, 1 – 2 – 3 – 5 为 3 条合法路径。

【测试数据与约定】

对于不同的测试点,我们约定各种参数的规模不会超过如下

测试点编号TT NN MM KK 是否有0边
155100
25100020000
351000200050
451000200050
551000200050
651000200050
751000002000000
8310000020000050
9310000020000050
10310000020000050

对于 100%的数据, 1 \le P \le 10^9,1 \le a_i,b_i \le N ,0 \le c_i \le 10001P109,1ai,biN,0ci1000 。

数据保证:至少存在一条合法的路线。


题解:

PS:百度上第一页的题解都是错的!错的!错的!!!(虽说能过)

点击打开链接


太神奇了!最后累加看的我有点迷。。需要好好理解


代码(洛谷会RE3个点的):

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cmath>
#define RG register
#define il inline
#define iter iterator
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
const int N=200005,inf=2e8;
int f[2][N],mod,n,m,K,head[N],nxt[N<<1],to[N<<1],dis[N<<1],num=0;
bool vis[N],imp[N];int Head[N];
void link(int x,int y,int z){
    nxt[++num]=head[x];to[num]=y;dis[num]=z;head[x]=num;}
void Link(int x,int y,int z){
    nxt[++num]=Head[x];to[num]=y;dis[num]=z;Head[x]=num;}

queue<int>q;
void priwork(bool t){
    for(int i=1;i<=n;i++)vis[i]=0,f[t][i]=inf;
    if(t==0)q.push(1),vis[1]=1,f[t][1]=0;
    else q.push(n),vis[n]=1,f[t][n]=0;
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=(t?Head[x]:head[x]);i;i=nxt[i]){
            RG int u=to[i];
            if(f[t][x]+dis[i]<f[t][u]){
                f[t][u]=f[t][x]+dis[i];
                if(!vis[u])vis[u]=1,q.push(u);
            }
        }
        vis[x]=0;
    }
}

int dp[N][55],d[N],sum=0,Q[N];
void solve(){
    for(int i=1;i<=n;i++)
        for(int j=head[i];j;j=nxt[j])
            if(f[0][i]+dis[j]==f[0][to[j]])d[to[j]]++;
    for(int i=1;i<=n;i++)if(!d[i])Q[++sum]=i;
    RG int t=0;int x,u;
    while(t!=sum){
        x=Q[++t];
        for(int i=head[x];i;i=nxt[i]){
            u=to[i];
            if(f[0][x]+dis[i]==f[0][u]){
                d[u]--;
                if(!d[u])Q[++sum]=u;
            }
        }
    }
}

void Clear(){
    memset(dp,0,sizeof(dp));
    for(RG int i=0;i<N;i++)Q[i]=d[i]=head[i]=Head[i]=imp[i]=0;
    sum=0;num=0;
}
inline void add(RG int &x,int y){x+=y;if(x>=mod)x-=mod;}
void work()
{
    Clear();
    int x,y,z;
    scanf("%d%d%d%d",&n,&m,&K,&mod);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        link(x,y,z);Link(y,x,z);
    }
    priwork(0);priwork(1);solve();
    
    for(int i=1;i<=n;i++)
        if(d[i]>0 && f[0][i]+f[1][i]<=f[0][n]+K){
            puts("-1");return ;
        }

    dp[1][0]=1;
    for(int k=0;k<=K;k++){
        for(int P=1;P<=sum;P++){
            int i=Q[P];
           if(!dp[i][k])continue;
            for(RG int j=head[i];j;j=nxt[j]){
                x=to[j];
                if(f[0][i]+dis[j]==f[0][x])
                    add(dp[x][k],dp[i][k]);
            }
        }
        for(RG int i=1;i<=n;i++){
            if(!dp[i][k])continue;
            for(RG int j=head[i];j;j=nxt[j]){
                x=to[j];
                if(f[0][i]+dis[j]!=f[0][x]
                    && f[0][i]+k+dis[j]-f[0][x]<=K)
                    add(dp[x][f[0][i]+k+dis[j]-f[0][x]],dp[i][k]);
            }
        }
    }
    int ans=0;
    for(int i=0;i<=K;i++)add(ans,dp[n][i]);
    printf("%d\n",ans);
}

int main()
{
    freopen("park.in","r",stdin);
    freopen("park.out","w",stdout);
    int T;cin>>T;
    while(T--)work();
    return 0;
}



2018.05.16:我傻逼,只要判断当前点到终点的最短距离上小于等于k的有没有0换即可。


代码:

#include<bits/stdc++.h>
using namespace std;
const int N=100003,M=200003;
struct node{
    int to,w,ne;
}e[M<<1];
int h1[N],hn[N],dis[N],n,m,K,P,in[N][51],f[N][51],tot,T,u,v,w;
char ss[1<<17],*A=ss,*B=ss;
#define gc getchar
template<class T>inline void read(T&x){
    char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c=='-')y=-1;x=c^48;
    while(c=gc(),47<c&&c<58)x=(x<<1)+(x<<3)+(c^48);x*=y;
}
namespace segment{
    int tr[N<<2],seg;
    void Set(int n){seg=1;while (seg<=n) seg<<=1;seg--;tr[0]=N-1;}
    void clr(){for (int i=1;i<=(seg<<1|1);i++) tr[i]=0;}
    int cmp(int a,int b){return dis[a]<dis[b]?a:b;}
    void mdy(int x,int w){for (int i=x+seg;dis[tr[i]]>w;i>>=1) tr[i]=x;dis[x]=w;}
    void del(int x){tr[x+=seg]=0;x>>=1;while (x) tr[x]=cmp(tr[x<<1],tr[x<<1|1]),x>>=1;}
}
using namespace segment;
void add(int u,int v,int w,int *h){
    e[++tot]=(node){v,w,h[u]};
    h[u]=tot;
}
void mod(int &x){x-=x>=P?P:0;}
void dij(){
    memset(dis,63,(n+1)*4);
    clr();mdy(n,0);
    for (int T=1;T<=n;T++){
        int u=tr[1];del(u);
        for (int i=hn[u],v;i;i=e[i].ne)
            if (dis[v=e[i].to]>dis[u]+e[i].w) mdy(v,dis[u]+e[i].w);
    }
}
int dfs(int u,int k){
    if (in[u][k]) return -1;
    if (f[u][k]) return f[u][k];
    in[u][k]=1;
    f[u][k]=u==n?1:0;
    for (int i=h1[u],v,tp,w;i;i=e[i].ne){
        if ((tp=dis[v=e[i].to]-dis[u]+e[i].w)<=k){
            if ((w=dfs(v,k-tp))==-1) return f[u][k]=-1;
            mod(f[u][k]+=w);
        }
    }
    in[u][k]=0;
    return f[u][k];
}
int main(){
    read(T);
    while (T--){
        read(n);read(m);read(K);read(P);
        tot=0;Set(n);
        memset(f,0,sizeof(f));
        memset(in,0,sizeof(in));
        memset(h1,0,(n+1)*4);
        memset(hn,0,(n+1)*4);
        while (m--){
            read(u);read(v);read(w);
            add(u,v,w,h1);add(v,u,w,hn);
        }
        dij();
        printf("%d\n",dfs(1,K));
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值