[洛谷P3953] 逛公園
洛谷題目鏈接:逛公園
題目復制過來有點炸格式就不貼了,看題目請戳上面的鏈接
題意: 給出一張\(n\)個點,\(m\)條邊的有向圖,設\(1\)到\(n\)的最短路徑長度為\(d\),問\(1\)到\(n\)路徑長度小於等於\(d+k\)的路徑有多少條.
題解: \(k=0\)的部分分就是一個最短路計數,如果不會的話可以先去看一下最短路計數.
我們先考慮不含\(0\)環的情況.首先我們可以求出從\(1\)為起點到其他點的最短路的長度,記為\(dist[i]\).最後對答案產生貢獻的狀態只有\(dist[n]+k\),所以我們可以記\(f[i][j]\)表示從\(1\)出發到\(i\)節點路徑長度為\(dist[i]+j\)
那麽這樣我們就可以寫出狀態轉移方程\((x->to)\):\[f[to][dist[x]+e[i].w-dist[to]+j] += f[x][j]\]
然後需要特別註意的是枚舉順序,我們在枚舉\(to\)的時候要先將\(x\)處理完,並且第二維的枚舉狀態要從小到大枚舉.處理好這些,就可以獲得\(70\)分了.
為什麽說是\(70\)分呢?因為我們這樣並不能判斷出無窮多條路徑的方案.但是我們可以發現,如果存在無窮多條路徑,那麽在\(1\)到\(n\)的最短路上(或是在一條\(1->n\)的路徑且長度小於等於\(dist[n]+k\)上)有一個\(0\)環,那麽就存在無窮多條路徑,因為可以在這個\(0\)
所以要判斷無窮多條路徑,就要先判斷是否存在一個\(0\)環在一條可以對答案產生貢獻的路徑上.這個可以用拓撲排序實現.
因為有可能存在\(0\)環,所以我們更新\(dp\)數組的時候不能再一邊遍歷圖,一遍更新數組了,這時候我們就需要對要更新的狀態排個序,也就是讓\(dist\)小的排在前面更新,但是又由於\(0\)邊的存在,我們需要讓一條邊的起點來更新終點,所以在多個點\(dist\)相同的時候,可以按照拓撲序來排.
細節比較多,可以自己多想想.
#include<bits/stdc++.h> using namespace std; const int N = 1e5+5; const int M = 2e5+5; const int K = 50+5; int T, n, m, k, yyj, ans = 0, dist[N][2], vis[N][2], point[N], in[N], ord[N]; int last[N][2], last0[N], ecnt[2], ecnt0 = 0; int f[N][K]; struct edge{ int to, nex, w; }e[M][2], e0[M]; struct node{ int id, dis; bool operator < (const node &a) const{ return dis > a.dis; } }; int gi(){ int res = 0, f = 1; char i = getchar(); while(i < '0' || i > '9'){ if(i == '-') f = -1; i = getchar(); } while(i >= '0' && i <= '9') res = res*10+i-'0', i = getchar(); return res*f; } void clear(){ memset(last, 0, sizeof(last)), ans = ecnt[0] = ecnt[1] = 0; memset(last0, 0, sizeof(last0)), ecnt0 = 0; memset(in, 0, sizeof(in)), memset(vis, 0, sizeof(vis)); memset(dist, 127/3, sizeof(dist)); memset(ord, 0, sizeof(ord)); } void add(int x, int y, int z, int t){ e[++ecnt[t]][t].to = y, e[ecnt[t]][t].w = z, e[ecnt[t]][t].nex = last[x][t], last[x][t] = ecnt[t]; } void add0(int x, int y){ e0[++ecnt0].to = y, e0[ecnt0].nex = last0[x], last0[x] = ecnt0; } void dijkstra(int st, int t){ priority_queue <node> q; q.push((node){ st, 0 }); dist[st][t] = 0; while(!q.empty()){ node x = q.top(); q.pop(); if(vis[x.id][t]) continue; vis[x.id][t] = 1; for(int to, i = last[x.id][t]; i; i = e[i][t].nex){ to = e[i][t].to; if(dist[to][t] > x.dis+e[i][t].w) dist[to][t] = x.dis+e[i][t].w, q.push((node){ to, dist[to][t] }); } } } bool cmpdis(int a, int b){ if(dist[a][0] == dist[b][0]) return ord[a] < ord[b]; return dist[a][0] < dist[b][0]; } bool topsort(){ queue <int> q; int cnt = 0; for(int i = 1; i <= n; i++) if(in[i] == 0) q.push(i), ord[i] = ++cnt; while(!q.empty()){ int x = q.front(); q.pop(); for(int to, i = last0[x]; i; i = e0[i].nex){ to = e0[i].to, in[to]--; if(in[to] == 0) q.push(to), ord[to] = ++cnt; } } for(int i = 1; i <= n; i++) if(in[i] && dist[i][0]+dist[i][1] <= dist[n][0]+k) return false; return true; } void solve(int st){ memset(f, 0, sizeof(f)), f[st][0] = 1; for(int i = 1; i <= n; i++) point[i] = i; sort(point+1, point+n+1, cmpdis); for(int l = 0; l <= k; l++){ for(int i = 1; i <= n; i++){ int x = point[i]; for(int j = last[x][0]; j; j = e[j][0].nex){ int to = e[j][0].to; if(dist[x][0]+e[j][0].w+l <= dist[to][0]+k) (f[to][dist[x][0]+e[j][0].w+l-dist[to][0]] += f[x][l]) %= yyj; } } } } int main(){ ios::sync_with_stdio(false); int x, y, z; T = gi(); while(T--){ clear(); n = gi(), m = gi(), k = gi(), yyj = gi(); for(int i = 1; i <= m; i++){ x = gi(), y = gi(), z = gi(), add(x, y, z, 0), add(y, x, z, 1); if(z == 0) add0(x, y), in[y]++; } dijkstra(1, 0), dijkstra(n, 1); if(!topsort()){ cout << -1 << endl; continue; } solve(1); for(int i = 0; i <= k; i++) (ans += f[n][i]) %= yyj; cout << ans << endl; } return 0; }
[洛谷P3953] 逛公園