Luogu 3953[NOIP2017] 逛公园 堆优化dijkstra + 记忆化搜索
题解
首先肯定是要求出单源最短路的,我用了堆优化dijikstra ,复杂度 mlogm,值得拥有!(只不过我在定义优先队列时把greater 打成了 less调了好久
然后我们就求出了$i$到源点的最短距离$dis_i$
定义一个数组 $f_{i, k}$表示从源点到节点$i$的距离比$dis_i$大$k$的路径数,另外一个数组$sch_{i,k}$ 记录某条路径上 该状态是否存在, 若在某条路径上出现了第二次, 并且$k <= K$,则可判断有符合条件的$0$环
对于要求的$f_{i, k}$ 需要求出它所有的前驱状态(也就是拓扑), 设它的某一前驱为 $f_{u,t}$则$t = dis_i + k - dis_u - w$ ,$w$为边权。
由于$t + dis_u + w = k + dis_i$, 所以$t - k = dis_i -dis_u - w$, 因为$dis_i$是到 $i$ 的最短距离, 则显然 $dis_i <= dis_u + w$, 得出$t <= k$, 也就是某个点前驱的 $k$值会不断减小,只需要$k >= 0$ 即可进行转移。
有转移方程 : $f_{i, k} = \sum f_{u,t}$ $t >= 0$。 边界为 $f_{1, 0} = 1$。但是若设$f_{1, 0} = 1$为边界状态的话,对于经过 点 $1$ 的0环就无法判断了。
所以我把边界定为$f_{0,0} = 1$,再从 $1 - > 0$连一条 记忆化搜索时需要记录前驱的反向,边权为0, 这样就可以解决$0$环的问题。
最后的答案$ans = \sum\limits_{k=0}^Kf_{n,k}$。
总时间复杂度为$O(MlogM + KN)$
代码
1 #include<cstring> 2 #include<algorithm> 3 #include<cstdio> 4 #include<queue> 5 #include<vector> 6 #define rd read() 7 using namespace std; 8 typedef pair<int,int> P;//用pair来存储dis 与点, 第一个关键字优先排序 9 10 const int N = 1e5 + 1e4; 11 12 int n, m, K, T, head[N], tot, dis[N], f[N][55], mod; 13 vector<int>q[N], w[N];//记录前驱的反向边 14 15 bool vis[N], sch[N][55], flag = true; 16 17 priority_queue<P ,vector<P>, greater<P> > pq;//优先队列 18 19 struct edge { 20 int u, v, c, nxt; 21 }e[N << 1]; 22 23 int read() { 24 int X = 0, p = 1; char c = getchar(); 25 for(; c > '9' || c < '0'; c = getchar() ) if( c == '-' ) p = -1; 26 for(; c >= '0' && c <= '9'; c = getchar() ) X = X * 10 + c - '0'; 27 return X * p; 28 } 29 30 void add( int u, int v, int c ) { 31 e[++tot].u = u; 32 e[tot].v = v; 33 e[tot].c = c; 34 e[tot].nxt = head[u]; 35 head[u] = tot; 36 } 37 38 void dij() {//求单源最短路 39 memset( vis, 0, sizeof(vis)); 40 memset( dis, 0x3f, sizeof(dis)); 41 pq.push(P(0, 1)); 42 dis[1] = 0; 43 for( P u, v; !pq.empty(); ) { 44 u = pq.top(); pq.pop(); 45 int x = u.second; 46 if(vis[x]) continue; 47 vis[x] = 1; 48 for( int i = head[x], nt; i; i = e[i].nxt ) if( dis[nt = e[i].v] > dis[x] + e[i].c && !vis[nt] ) { 49 dis[nt] = dis[x] + e[i].c; 50 pq.push(P(dis[nt], nt)); 51 } 52 } 53 } 54 55 int dp( int u, int k ) {//记忆化搜索 56 if( u == 0 && k == 0 ) return 1; 57 if( f[u][k] != -1 ) return f[u][k]; 58 f[u][k] = 0; 59 sch[u][k] = 1; 60 for( int i = 0; i < (int)q[u].size(); ++i ) { 61 int nt = q[u][i], t = dis[u] + k - dis[nt] - w[u][i]; 62 if(t < 0) continue; 63 if( sch[nt][t] ) flag = false;//记录路径上的状态 64 f[u][k] = (f[u][k] + dp(nt, t)) % mod; 65 } 66 sch[u][k] = 0;//回溯 67 return f[u][k]; 68 } 69 70 int main() 71 { 72 T = rd; 73 for(; T; T-- ) { 74 flag = true; 75 memset(f, -1, sizeof(f)); 76 memset(head, 0, sizeof(head)); 77 memset(sch, 0, sizeof(sch)); 78 tot = 0; 79 for( int i = 1; i <= n; ++i ) q[i].clear(), w[i].clear(); 80 n = rd; m = rd; K = rd; mod = rd; 81 for( int i = 1; i <= m; ++i ) { 82 int u = rd, v = rd, c = rd; 83 add(u, v, c); 84 q[v].push_back(u); 85 w[v].push_back(c); 86 } 87 q[1].push_back(0); 88 w[1].push_back(0); 89 dij(); 90 dis[0] = 0; 91 int ans = 0; 92 for( int i = 0; i <= K; ++i ) ans = (ans + dp(n, i)) % mod; 93 if(!flag) printf("-1\n"); 94 else printf("%d\n", ans); 95 } 96 }