【NOIP2017提高】逛公園(最短路+記憶化搜尋)
阿新 • • 發佈:2019-01-31
原題見洛谷。
分析
1,既然需要最短路做基礎,所以需要做一遍最短路演算法。
2,有的點可能到不了N,所以正反各建一個圖,用反向圖跑出dis[i]表示i距離N的最短距離。
3,K最大為50,所以考慮dp的做法。設f[i][k]表示i到N,實際距離-dis[i]<=k 時的方案數,也就是f[i][K]=∑f[i][j],(0<=j<=K)。
4,用記憶化搜尋,分析邊界,f[N][0]=1。
分析狀態轉移方程,如果當前節點為u,比dis[u]大k,現在列舉到一條邊(u,v,w)。
如果u不走最短路,改走v這邊,那麼路會變長:dis[v]+w-dis[u](如果本來就是v這邊最短就等於0),那麼v再往後走時,只需要比dis[v]多出:k-(dis[v]+w-dis[u])即可,利用記憶化搜尋可以快速出解。
5,存在0環的情況。設一個vis陣列,表示(i,k)是否已經入棧,若存在0環,那麼(i,k)還未出棧時又會返回(i,k),這裡只需要一維存i入棧時的k即可。如果存在0環迅速退出輸出-1,。
6,常數優化。除了上述的優化到一維,記得輸入優化,用memset而不是for,邊陣列可以不必清空。
7,注意細節,比如程式碼中dfs裡,if(f[i][k_now]>=0)一句不加“=”會導致超時。
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int MAXN=100005; const int MAXM=200005; int N,M,T,P,K,np,np2,flag,last[MAXN],last2[MAXN],dis[MAXN],in_ring[MAXN],vis[MAXN]; LL f[MAXN][51]; struct edge{int to,w,pre;}; edge E[MAXM],E2[MAXM]; char c; void scan(int &x) { for(c=getchar();c<'0'||c>'9';c=getchar()); for(x=0;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0'; } void init() { np=0;np2=0;flag=0; memset(last,0,sizeof(last)); memset(last2,0,sizeof(last2)); memset(dis,0x3f,sizeof(dis)); memset(f,-1,sizeof(f)); memset(vis,0,sizeof(vis)); memset(in_ring,-1,sizeof(in_ring)); } void addedge(int u,int v,int w) { E[++np]=(edge){v,w,last[u]}; last[u]=np; } void addedge2(int u,int v,int w) { E2[++np2]=(edge){v,w,last2[u]}; last2[u]=np2; } void SPFA() { queue<int>q; dis[N]=0; q.push(N); while(!q.empty()) { int i=q.front(); q.pop(); vis[i]=0; for(int p=last2[i];p;p=E2[p].pre) { int j=E2[p].to; if(dis[j]>dis[i]+E2[p].w) { dis[j]=dis[i]+E2[p].w; if(!vis[j]) { vis[j]=1; q.push(j); } } } } } int dfs(int i,int k_now) { if(in_ring[i]==k_now) //出現0環 { flag=1; return 0; } if(f[i][k_now]>=0) return f[i][k_now]; //記憶化 in_ring[i]=k_now; //進入當前環中 int sum=0; for(int p=last[i];p;p=E[p].pre) { int j=E[p].to,t=k_now-(dis[j]+E[p].w-dis[i]); //下一步的差值 if(t<0||t>K) continue; //越界 sum=(sum+dfs(j,t))%P; if(flag) return 0; //如果出現了0環 } in_ring[i]=-1; //退出環 if(i==N&&k_now==0) sum=1; //邊界 return f[i][k_now]=sum; } void solve() { int u,v,w,i; init(); scan(N);scan(M);scan(K);scan(P); for(i=1;i<=M;i++) { scan(u);scan(v);scan(w); addedge(u,v,w); addedge2(v,u,w); } SPFA(); int ans=0; for(i=0;i<=K;i++) { ans=(ans+dfs(1,i))%P; if(flag) break; } if(flag) cout<<-1<<'\n'; else cout<<ans<<'\n'; } int main() { scan(T); while(T--) solve(); return 0; }