洛谷P3953 逛公园

传送门

设dp[i][j]为从1到i长度为最短路+j的路径数量,dis1为从1到每个点的最短距离,disn为从n到每个点的最短距离。先不考虑0边,dp[u][j]可以转移到v的dis[u]+w(u,v)-dis[v]+j,即dp[u][j]->dp[v][dis[u]+w(u,v)-dis[v]+j]。dis[u]+w(u,v)-dis[v]即松弛距离。在有向图上,可能前面的点会由后面的点松弛到,因此dp转移的时候外层枚举0->k,内层枚举按dis1排序的节点,分层转移。

接下来考虑0边的影响。0边可能会产生0环,如果0环恰好在有效路径内,则会出现无穷条路径满足情况。如果0环上的点满足dis1[i]+disn[i]<=dis1[n]+k,那么这个0环跑无穷多次都是满足题意的,判断这种情况输出-1。于是要找出在0环上的点,把所有0边取出来建图进行拓扑排序,如果排序完有入度不为0的点则存在0环。但跑一次拓扑只是把所有指向0环的边摘掉了,可能还有0环指向的边存在于图中。因此建0边的反图,再跑一次拓扑排序,把所有原图中0环指向的边也摘掉,剩下的既被0边连接又没有被两次拓扑摘掉的点就是0环上的点。

存在0边以后,如果dp时仍然只按dis1排序,在0边相连的点之间可能会存在顺序问题。例如,存在x->y->z,中间相连的两条边都为0,那么转移的时候应该由x到y再到z,实际上因为三个点的dis1相同,可能不会按正确顺序排序。那么在第一次拓扑排序的时候顺便记录与0边相连的点的拓扑序,dp时排序以dis1为第一关键字,拓扑序为第二关键字即可。

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
const int inf=214748364;
int a[100010],now;
int t,n,m,k,p,ru[100010],tag[100010],flag,zero[100010],zer,vis1[100010],vis2[100010];
long long ans;
int dis[100010],vis[100010];
bool cmp(int x,int y){
    if(dis[x]==dis[y])return tag[x]<tag[y];
    else return dis[x]<dis[y];
}
struct edg{
    int x,y;
    edg(int a=0,int b=0){
        x=a,y=b;
    }
}ze[200010];
int ver[200010],Next[200010],head[100010],tot,edge[200010];
void add(int x,int y,int z){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
}
int ver1[200010],Next1[200010],head1[100010],tot1,edge1[200010];
void add1(int x,int y,int z){
    ver1[++tot1]=y;
    Next1[tot1]=head1[x];
    head1[x]=tot1;
    edge1[tot1]=z;
}
priority_queue<pair<int,int> >q;
void dij(){
    while(q.size())q.pop();
    for(int i=1;i<=n;i++)dis[i]=inf,vis[i]=0;
    dis[1]=0;
    q.push(make_pair(0,1));
    while(!q.empty()){
        while(q.size()&&vis[q.top().second])q.pop();
        if(q.empty())break;
        int x=q.top().second;
        q.pop();
        vis[x]=1;
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(dis[y]>dis[x]+edge[i]){
                dis[y]=dis[x]+edge[i];
                q.push(make_pair(-dis[y],y));
            }
        }
    }
}
int dis1[100010];
void dij1(){
    while(q.size())q.pop();
    for(int i=1;i<=n;i++)dis1[i]=inf,vis[i]=0;
    dis1[n]=0;
    q.push(make_pair(0,n));
    while(!q.empty()){
        while(q.size()&&vis[q.top().second])q.pop();
        if(q.empty())break;
        int x=q.top().second;
        q.pop();
        vis[x]=1;
        for(int i=head1[x];i;i=Next1[i]){
            int y=ver1[i];
            if(dis1[y]>dis1[x]+edge1[i]){
                dis1[y]=dis1[x]+edge1[i];
                q.push(make_pair(-dis1[y],y));
            }
        }
    }
}
queue<int>qq;
void tp1(){
    while(qq.size())qq.pop();
    for(int i=1;i<=n;i++){
        if(!ru[i]&&zero[i])qq.push(i);
    }
    while(qq.size()){
        int x=qq.front();
        qq.pop();
        tag[x]=++now;
        vis1[x]=1;
        for(int i=head1[x];i;i=Next1[i]){
            int y=ver1[i];
            ru[y]--;
            if(!ru[y]){
                qq.push(y);
            }
        }
    }
}
void tp2(){
    while(qq.size())qq.pop();
    for(int i=1;i<=n;i++){
        if(!ru[i]&&zero[i])qq.push(i);
    }
    while(qq.size()){
        int x=qq.front();
        qq.pop();
        vis2[x]=1;
        for(int i=head1[x];i;i=Next1[i]){
            int y=ver1[i];
            ru[y]--;
            if(!ru[y]){
                qq.push(y);
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(zero[i]&&!vis1[i]&&!vis2[i]){
            if(dis[i]+dis1[i]<=dis[n]+k)flag=1;
        }
    }
}
long long dp[100010][51];
void work(){
    dp[1][0]=1%p;
    for(int i=0;i<=k;i++){
        for(int j=1;j<=n;j++){
            int x=a[j];
            if(!dp[x][i])continue;
            for(int l=head[x];l;l=Next[l]){
                int y=ver[l];
                if(i+dis[x]+edge[l]-dis[y]<=k){
                    dp[y][i+dis[x]+edge[l]-dis[y]]=(dp[y][i+dis[x]+edge[l]-dis[y]]+dp[x][i])%p;
                }
            }
        }
    }
}
void clear(){
    now=zer=ans=tot=tot1=flag=0;
    memset(dp,0,sizeof(dp));
    memset(ru,0,sizeof(ru));
    memset(head,0,sizeof(head));
    memset(head1,0,sizeof(head1));
    memset(tag,0,sizeof(tag));
    memset(vis1,0,sizeof(vis1));
    memset(vis2,0,sizeof(vis2));
    memset(zero,0,sizeof(zero));
}
int main(){
//    freopen("1.in","r",stdin);
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d%d",&n,&m,&k,&p);
        clear();
        for(int i=1,x,y,z;i<=m;i++){
            scanf("%d%d%d",&x,&y,&z);
            if(!z){
                ze[++zer]=edg(x,y);
                zero[x]=zero[y]=1;
            }
            add(x,y,z);
            add1(y,x,z);
        }
        dij();
        dij1();
        memset(head1,0,sizeof(head1));
        tot1=0;
        for(int i=1;i<=zer;i++){
            add1(ze[i].x,ze[i].y,0);
            ru[ze[i].y]++;
        }
        tp1();
        memset(head1,0,sizeof(head1));
        memset(ru,0,sizeof(ru));
        tot1=0;
        for(int i=1;i<=zer;i++){
            add1(ze[i].y,ze[i].x,0);
            ru[ze[i].x]++;
        }
        tp2();
        if(flag){
            printf("-1\n");
            continue;
        }
        for(int i=1;i<=n;i++){
            a[i]=i;
        }
        sort(a+1,a+n+1,cmp);
        work();
        for(int i=0;i<=k;i++){
            ans=(ans+dp[n][i])%p;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
View Code

 

posted @ 2019-11-13 22:01  Chloris_Black  阅读(153)  评论(0编辑  收藏  举报