逛公园

30 篇文章 0 订阅

一、题目

点此看题

二、解法

0x01 k=0
直接写最短路计数(可以顺手A这个这个)。
0x02 没有0边
考虑到这道题 k k k最大只有 50 50 50,且过程中 k k k一定不减,把 k k k放进 d p dp dp中,定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为到 i i i多走了 j j j步(相较于最短路),有:
d p [ v ] [ l + d e l t a ] = ∑ d p [ u ] [ l ] dp[v][l+delta]=\sum dp[u][l] dp[v][l+delta]=dp[u][l] u , v u,v u,v相连)
其中 d e l t a delta delta表示走这条路带来的增量, d e l t a = d i s [ u ] + c − d i s [ v ] delta=dis[u]+c-dis[v] delta=dis[u]+cdis[v],易得 d e l t a delta delta非负。
这个 d p dp dp O ( m k ) O(mk) O(mk)的,似乎可以 A A A,但发现还存在一个问题,就是我们无法保证 d p [ u ] [ l ] dp[u][l] dp[u][l]更新完了后再去更新 d p [ v ] [ l + d e l t a ] dp[v][l+delta] dp[v][l+delta],也就是这个算法的正确性被限制在了 d p dp dp的顺序上。
如果 d e l t a delta delta为正,我们可以通过先枚举 l l l来解决,而对于 d e l t a delta delta 0 0 0的情况,我们暂时不考虑 0 0 0边,直接将 d i s dis dis排序, d i s dis dis小的先更新,这样就能解决顺序问题。
(注:这种算法过不了样例,但卡卡常 70 70 70没问题)。
0x03 正解
介绍一种很优秀的做法,考虑到不会带来增量的边在且仅在最短路径上,我们可以通过跑正反向图的方式找出所有不会带来增量的边,然后直接拓扑排序,有环直接输出 − 1 -1 1,没有环按拓扑序 d p dp dp即可。
为什么能这样做呢?这是因为组成环的只有 0 0 0边(如果有非零边那么跑一遍环就不可能是最短路了)。 m e a n w h i l e meanwhile meanwhile,没有带来增量的边应该放在最短路上处理,从 1 1 1开始拓扑保证了顺序是符合最短路的。
注意 d p dp dp时先 d p dp dp没有增量的边,然后在 d p dp dp有增量的边就可以了。
0x04 代码
我的思路来源于这位大佬的博客,下面附上我卡过常的代码。

#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
const int MAXN = 100005;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int T,n,m,k,p,tot,ans,cnt,a[2*MAXN],b[2*MAXN],w[2*MAXN],tp[MAXN],in[MAXN],f[MAXN],dis[MAXN][2],dp[MAXN][51];
struct edge
{
    int v,c,next;
} e[2*MAXN];
struct node
{
    int u,c;
    bool operator < (node x) const
    {
        return c>x.c;
    }
};
void dijkstra(int cur)
{
    priority_queue<node> q;
    q.push(node{cur?n:1,0});
    dis[cur?n:1][cur]=0;
    while(!q.empty())
    {
        node t=q.top();
        q.pop();
        for(int i=f[t.u]; i; i=e[i].next)
        {
            int v=e[i].v,c=e[i].c;
            if(dis[v][cur]>t.c+c)
            {
                dis[v][cur]=t.c+c;
                q.push(node{v,dis[v][cur]});
            }
        }
    }
}
bool topol()
{
    if(in[1]) return 0;
    queue<int> q;
    q.push(1);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        tp[++cnt]=u;
        for(int i=f[u]; i; i=e[i].next)
        {
            int v=e[i].v;
            in[v]--;
            if(in[v]==0)
                q.push(v);
        }
    }
    for(int i=1; i<=n; i++)
        if(in[i])
            return 0;
    return 1;
}
void makeSet()
{
    tot=0;
    for(int i=1; i<=n; i++)
        f[i]=0;
}
int main()
{
    T=read();
    while(T--)
    {
        n=read();
        m=read();
        k=read();
        p=read();
        tot=cnt=ans=0;
        memset(dis,0x3f,sizeof dis);
        for(int i=1; i<=n; i++)
        {
            f[i]=in[i]=0;
        }
        for(int i=1; i<=n; i++)
            for(int j=0; j<=k; j++)
                dp[i][j]=0;
        for(int i=1; i<=m; i++)
        {
            int u=read(),v=read(),c=read();
            a[i]=u;
            b[i]=v;
            w[i]=c;
            e[++tot]=edge{v,c,f[u]},f[u]=tot;
        }
        dijkstra(0);
        makeSet();
        for(int i=1; i<=m; i++)
            e[++tot]=edge{a[i],w[i],f[b[i]]},f[b[i]]=tot;
        dijkstra(1);
        makeSet();
        for(int i=1; i<=m; i++)
        {
            int u=a[i],v=b[i],c=w[i];
            if(dis[u][0]+dis[v][1]+c<=dis[n][0]+k && dis[u][0]+c==dis[v][0])
            {
                e[++tot]=edge{v,0,f[u]},f[u]=tot;
                in[v]++;
            }
        }
        if(!topol())
        {
            puts("-1");
            continue ;
        }
        dp[1][0]=1;
        for(int l=0; l<=k; l++)
        {
            for(int i=1; i<=cnt; i++)
            {
                int u=tp[i];
                for(int j=f[u]; j; j=e[j].next)
                {
                    int v=e[j].v;
                    dp[v][l]+=dp[u][l];
                    dp[v][l]%=p;
                }
            }
            for(int i=1; i<=m; i++)
            {
                int u=a[i],v=b[i],c=w[i],delta=dis[u][0]+c-dis[v][0];
                if(delta+l<=k && delta)
                {
                    dp[v][l+delta]+=dp[u][l];
                    dp[v][l+delta]%=p;
                }
            }
        }
        for(int i=0; i<=k; i++)
            ans=(ans+dp[n][i])%p;
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值