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
",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
");
            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
*/
原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/13953290.html