NOIP2017逛公園 記憶化搜尋+判環+最短路
阿新 • • 發佈:2018-12-14
主要思路:
由於這道題K的數值範圍較小,故可以直接列舉K來求答案。答案就是起點到終點的為d,d+1,d+2,...,d+K的路徑的條數總合。
聯想到之前一些在路徑上有消耗的圖論題,我們可以把這個K也當做是一個被消耗的東西,就像錢一樣(以下就當錢講好了)。故我們可以定義dp[i][j]為從i這個點出發走比他到終點的最短距離大j的路徑有多少條。接下來就是狀態轉移的問題了 。 記你的當前所在的點X到終點的最短距離為Dis1,現在你要走到點Y,他到終點的最短距離為Dis2,X到Y的邊長為D。那麼你從X走Y到終點的最短距離為Dis2+D,而你走點X到終點的最短路徑(不用管有沒有經過Y)為Dis1,故消耗的錢就是Dis2+D-Dis1,即為cost。故dp[X][j]=∑dp[Y][j-cost](若錢不夠是不能走的,但是錢剛好花完是可以走的,也就是不要欠錢就可以了一直走下去)終止狀態就是終點且剩餘錢數為0,這樣只有一種方案。(到達終點時若策策還有錢,就讓策策去把錢正好花完,花不完是不能計數的)。 那麼就能發現如果能走到一個0環上且手上的錢不為負數,那麼他可以沿著0環隨便走多少步再到終點(0環上的點到終點的最短距離都是一樣的),那麼這樣就得輸出-1,但是前提條件是他能用手上的錢走到0環。 判0環就讓記錄在這條路上你上次到達這個點還剩多少錢,若與這個點一樣就說明這個點在0環上。(可能有些人會問,從0環走出來沒辦法正好把錢花完走向終點怎麼辦,出門少帶錢就可以了(如果你沒有和我一樣的疑問可以忽略)) 每個點到終點的最短距離就建一個方向圖跑最短路就可以了。
AC程式碼:
#include<cstdio> #include<cstring> #include<queue> #define M 200005 using namespace std; int dis[M]; struct Graph { struct E { int to,nx,d; } edge[M]; int tot,head[M]; void Init() {memset(head,0,sizeof(head)),tot=0;} void Addedge(int a,int b,int d) { edge[++tot].to=b; edge[tot].nx=head[a]; edge[tot].d=d; head[a]=tot; } } G1,G2; struct Dijstra { struct node { int id,d; bool operator <(const node &x)const { return d>x.d; } node(int id,int d):id(id),d(d) {} }; priority_queue<node>Q; int cnt[M]; bool mark[M]; void solve(int st) { Q.push(node(st,0)); memset(dis,63,sizeof(dis)); memset(mark,0,sizeof(mark)); dis[st]=0; while(!Q.empty()) { int now=Q.top().id; Q.pop(); if(mark[now])continue; mark[now]=1; for(int i=G2.head[now]; i; i=G2.edge[i].nx) {//在反向圖上跑每個點到終點的最短路 int nxt=G2.edge[i].to; if(dis[now]+G2.edge[i].d<dis[nxt]) { dis[nxt]=dis[now]+G2.edge[i].d; Q.push(node(nxt,dis[nxt])); } } } } } S; int P,K,n; bool have_ring; bool in_ring[M][55]; int dp[M][55];//dp[i][j]表示從i出發,比最短路多走的路程為j的方案總數 int dfs(int now,int now_k) { if(in_ring[now][now_k]) { have_ring=1;//判是否存在0環 return 0; } if(dp[now][now_k]>=0)return dp[now][now_k];//記憶化 in_ring[now][now_k]=1; int& res=dp[now][now_k]; res=0; for(int i=G1.head[now]; i; i=G1.edge[i].nx) { int nxt=G1.edge[i].to,nxtd=now_k-(G1.edge[i].d-(dis[now]-dis[nxt]));//nxtd為剩下的錢 if(nxtd<0||nxtd>K)continue;//超出邊界,不能走 res=(res+dfs(nxt,nxtd))%P; dfs(nxt,nxtd); if(have_ring)return 0;//如果有環就直接退出 } in_ring[now][now_k]=0; if(now==n&&now_k==0)res=1;//到達終點放在這裡判,防止終點在0環上。注意只有錢花完且到達終點才停止。 return res; } void Init() { have_ring=0; memset(in_ring,0,sizeof(in_ring)); memset(dp,-1,sizeof(dp)); G1.Init(),G2.Init(); } int main() { int T; scanf("%d",&T); while(T--) { Init(); int m; scanf("%d%d%d%d",&n,&m,&K,&P); for(int i=1; i<=m; i++) { int a,b,d; scanf("%d%d%d",&a,&b,&d); G1.Addedge(a,b,d);//建正向邊 G2.Addedge(b,a,d);//建反向邊 } S.solve(n);//預處理終點到每個點的最短距離 int ans=0; for(int i=0; i<=K; i++) {//迴圈從0開始 ans=(ans+dfs(1,i))%P; if(have_ring)break; } if(have_ring)puts("-1"); else printf("%d\n",ans); } return 0; }