[洛谷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;
}