[题解]luogu_P3593_[NOIP2017]逛公园(最短路相关计数

这题和最短路计数有很大关系,再加上k只有50,容易让人想到dp,

先设计状态$f[i][j]$表示到$i$点比最短路多走了$j$长度的方案数,接下来思考转移

考虑一条边$u->v$边权为$w$,从$f[u][j]$能转移到$f[v]$的哪个状态?

既然是走到$v$比$dis[v]$多走多少,就比较显然了,我们已经走了$dis[u]+j$,从这条边走到$v$再花费$w$的边权,总共比$dis[v]$多走$dis[u]+w-dis[v]$,可转移到$f[v][j-(dis[u]+w-dis[v])]$

(虽然显然但是看题解的时候真不知道啥意思...前面的题解全给化简了...看不出来是啥意义...

而这个式子(显然)有一个性质,就是$dis[u]+w-dis[v]>=0$,当取等时表示这条就是最短路

那么这样转移就会有序无后效性,我们从小到大枚举K转移即可

至于-1的情况应该是出现0环,样例中也有提示,我们可以用dfs,$v[x][res]$如果被重复访问(不花费任何代价就回到这个点)即为-1

可以使用记忆化搜索dp,引用某题解:因为不是每个节点都能够到达节点n的,毕竟这是有向图,反向建图的好处就是可以完全回避进入死胡同的情况,同时也是为了spfa算出到节点n的值,这是一种常用的技巧。似乎不建反图也是对的

还有一些实现细节在代码里需要注意

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100009;
const int maxm=200009;
inline int read(){
    int ret=0,fix=1;char ch;
    while(!isdigit(ch=getchar()))fix=ch=='-'?-1:fix;
    do ret=(ret<<1)+(ret<<3)+ch-'0';
    while(isdigit(ch=getchar()));
    return ret*fix;
}
int n,m,k,mod;
struct node{
    int v,nxt,w;
}e[maxm<<1],e2[maxm<<1];
int head[maxn],cnt,head2[maxn],cnt2;
inline void add(int u,int v,int w){
    e[++cnt].v=v;e[cnt].w=w;e[cnt].nxt=head[u];head[u]=cnt;
}
inline void add2(int u,int v,int w){
    e2[++cnt2].v=v;e2[cnt2].w=w;e2[cnt2].nxt=head2[u];head2[u]=cnt2;
}
int d[maxn];
inline void spfa(){
    memset(d,0x7f,sizeof(d));
    queue<int> q;
    q.push(1);
    d[1]=0;
    while(!q.empty()){
        int x=q.front(); q.pop();
        for(int i=head[x];i;i=e[i].nxt){
            int y=e[i].v;
            if(d[y]>d[x]+e[i].w){
                d[y]=d[x]+e[i].w;
                q.push(y);
            }
        }
    }
}
bool flag,vis[maxn][55];
ll f[maxn][55];
int dfs(int x,int res){
    if(res<0 || res>k)return 0;
    if(vis[x][res]){
        vis[x][res]=0;
        return -1;
    }
    if(f[x][res]!=-1)return f[x][res];
    vis[x][res]=1;
    ll ans=0;
    for(int i=head2[x];i;i=e2[i].nxt){
        int y=e2[i].v;
        int t=dfs(y,res-(d[y]+e2[i].w-d[x]));//反图所以xy互换 
        if(t!=-1)(ans+=t)%=mod;
        else{
            vis[x][res]=0;
            return -1;
        }
    }
    vis[x][res]=0;
    if(x==1 && res==0)(ans+=1)%=mod;//找到 
    f[x][res]=ans;
    return ans;
}
int main(){
//    freopen("testdata (1).in","r",stdin);
    int T=read();
    while(T--){
        scanf("%d%d%d%d",&n,&m,&k,&mod);
        cnt=0;cnt2=0;
        memset(e2,0,sizeof(e2));
        memset(head2,0,sizeof(head2));
        memset(e,0,sizeof(e));
        memset(head,0,sizeof(head));
        for(int i=1,u,v,w;i<=m;i++){
            u=read(),v=read(),w=read();
            add(u,v,w);add2(v,u,w);
        }
        spfa();
        memset(f,-1,sizeof(f));
        memset(vis,0,sizeof(vis));
        flag=0;
        ll ans=0;
        for(int i=0;i<=k;i++){
            int t=dfs(n,i);
            if(t==-1)flag=1;
            else (ans+=t)%=mod;
        }
        printf("%d\n",flag==1?-1:ans);
    }
}

 

posted @ 2019-09-06 21:03  羊肉汤泡煎饼  阅读(177)  评论(0编辑  收藏  举报