1. 程式人生 > >洛谷P3953 逛公園(NOIP2017)(最短/長路,拓撲排序,動態規劃)

洛谷P3953 逛公園(NOIP2017)(最短/長路,拓撲排序,動態規劃)

洛谷題目傳送門

又是一年聯賽季。NOIP2017至此收官了。

這個其實是比較套路的圖論DP了,但是細節有點噁心。

先求出\(1\)到所有點的最短路\(d1\),和所有點到\(n\)的最短路\(dn\)

\(f_{i,j}\)表示\(i\)號點,所有與\(d1\)差距不超過\(j\)的路徑條數。轉移的時候肯定是從小到大列舉\(j\),再列舉邊轉移。顯然每條邊都有一個\(\Delta\)值,為\(d1_x-d1_y+w\),含義就是強制經過這條邊的最短路長度相較於原最短路長度的增量。於是有轉移式\(f_{x,j}\rightarrow f_{y,j+\Delta}\)

顯然上面的轉移,要沿著最短路的方向轉移,所以要按\(d1\)

從小到大考慮每個點的出邊。

沒有\(0\)的邊就可以直接開始做了,可偏偏就是有啊qwq。

首先來判無解,首先要有個\(0\)環,其次要有一條經過環的、長度小於等於\(d1_n+k\)的路徑。考慮在所有\(0\)邊的子圖上拓撲排序,剩下未出隊的肯定在環上,對剩下的點檢查一遍有沒有\(d1_x+dn_x\le d1_n+k\)的。

接下來,還別忘了處理被\(0\)邊所連起來的點的轉移順序。這個時候如果剩下環肯定貢獻不到答案,需要處理的就是一些DAG上的點的順序了。顯然來一組拓撲序就可以了,拓撲排序的時候順便搞搞。轉移順序就用\(d1\)和拓撲序雙關鍵字確定好了。

時間複雜度\(O(T((n+m)\log m+kn))\)

#include<bits/stdc++.h>
#define LL long long
#define RG register
#define R RG int
#define G if(++ip==ie)fread(ip=buf,1,SZ,stdin)
using namespace std;
const int SZ=1<<19,N=1e5+9,M=4e5+9;
int n,m,k,YL,p,he[N],re[N],ne[M],to[M],w[M],deg[N],q[N],d1[N],dn[N],o[N],ord[N],f[N][51];
bool vis[N];
struct Node{
    int d,x;
    inline bool operator<(Node a)const{
        return d>a.d||(d==a.d&&o[x]>o[a.x]);
    }
};
priority_queue<Node>Q;
char buf[SZ],*ie=buf+SZ,*ip=ie-1;
inline int in(){
    G;while(*ip<'-')G;
    R x=*ip&15;G;
    while(*ip>'-'){x*=10;x+=*ip&15;G;}
    return x;
}
inline void add(R&x,R y){
    if((x+=y)>=YL)x-=YL;
}
void dij(R*d,R*he,R x){//最短路
    memset(d+1,127,4*n);memset(vis+1,0,n);
    Q.push((Node){d[x]=0,x});
    while(!Q.empty()){
        x=Q.top().x;Q.pop();
        if(vis[x])continue;
        vis[x]=1;
        if(d==d1)ord[++p]=x;//記錄轉移順序
        for(R y,i=he[x];i;i=ne[i])
            if(d[y=to[i]]>d[x]+w[i])
                Q.push((Node){d[y]=d[x]+w[i],y});
    }
}
int main(){
    for(R T=in();T;--T){
        n=in();m=in();k=in();YL=in();
        R x,y,t=0;
        for(R i=1;i<=m;++i){
            x=in(),y=in();//建雙向邊
            ne[i]=he[x];to[he[x]=i]=y;i+=m;
            ne[i]=re[y];to[re[y]=i]=x;i-=m;
            if(!(w[i]=w[i+m]=in()))++deg[y];
        }
        for(R i=1;i<=n;++i)//拓撲排序
            if(!deg[i])q[++t]=i;
        for(R h=0;h<=t;++h){
            o[x=q[h]]=h;
            for(R i=he[x];i;i=ne[i])
                if(!w[i]&&!--deg[to[i]])q[++t]=to[i];
        }
        dij(d1,he,1);dij(dn,re,n);
        for(R i=1;i<=n;++i)
            if(deg[i]&&d1[i]+dn[i]<=d1[n]+k){puts("-1");goto F;}
        for(x=1;x<=n;++x)//重賦邊權為Δ
            for(R i=he[x];i;i=ne[i])
                w[i]+=d1[x]-d1[to[i]];
        f[1][0]=1;//DP開始
        for(R j=0;j<=k;++j)
            for(R i=1;i<=n;++i)
                if(f[x=ord[i]][j])
                    for(R i=he[x];i;i=ne[i])
                        if(j+w[i]<=k)add(f[to[i]][j+w[i]],f[x][j]);
        for(R i=x=0;i<=k;++i)add(x,f[n][i]);
        printf("%d\n",x);
      F:memset(he+1,0,4*n);memset(re+1,0,4*n);
        memset(o+1,0,4*n);memset(deg+1,0,4*n);memset(ord+1,0,4*n);
        memset(f+1,0,4*51*n);p=0;
    }
    return 0;
}