30pts
可以发现,\(k=0\)的情况下,问题转化为最短路计数,即从起点\(s\)到每个点有多少最短路。
跑最短路的时候顺便维护\(ans[u]\),表示从\(s\)到\(u\)的最短路方案,讨论如下:
①当\(dist[v]>dist[u]+val[u][v]\)时,\(ans[v]=ans[u];\)
②当\(dist[v]=dist[u]+val[u][v]\)时,\(ans[v]+=ans[u]。\)
答案即是\(ans[n]\)。
100pts
1.动态规划
首先不考虑\(w=0\)的边的处理。
容易发现,\(k\leq50\),故考虑枚举k的值,问题转化为求解每个k下的最短路方案数个数。
令\(dp[u][k]\)表示当前在第\(u\)个点,最短路距离为\(dist[u]+k\)的方案数。答案即为\(\sum_{i=0}^{50} dp[n][i]\)。
想要得知终点的\(dp\)方程状态,那么从起点建正图来推状态是比较复杂的,故不妨从终点\(n\)倒推状态。
考虑\(dp[v][x]->dp[u][k]\)的状态转移,发现\(k\)可以由\(v\)转移过来:
\[(dist[v]+x)+val[v][u]=dist[u]+k \]则
\[k=dist[v]+x+val[v][u]-dist[u] \]故
\[dp[u][k]=\sum_vdp[v][dist[v]+x+val[v][u]-dist[u]] \]复杂度\(O(km)\)
2.无穷多解处理
可以发现,若通过的道路上出现一个权值均为0的环,那一定会出现无穷多解。但有零环不代表一定存在无穷解。
考虑一个特殊数据:
显然的是,2-4的零环并不影响最短路方案,但是4-5-6的零环会产生无穷多解。
故我们首先用\(tarjan\)找出零环,对这条零环上的点\(u\)(任一点即可,对答案的贡献一致)进行如下判断\(^{[1]}\):
若
\[dist[u]+dist2[u]\leq dis[n]+k \]其中\(dist[u]\)代表\(1-u\)的最短路,\(dist2[u]\)代表\(u-n\)的最短路。
那么说明这条零环位于最短路上,说明存在无穷解。
综上:
对整张图分别正反建边,并跑一遍最短路;跑一遍\(tarjan\)寻找零环,判断零环是否导致无穷解即可。
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<stack>
int T;
int n, m, k, p;
int X, Y, Z;
int ans;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
int nxt[maxn << 1], to[maxn << 1], head[maxn], val[maxn << 1];
int nxt2[maxn << 1], to2[maxn << 1], head2[maxn], val2[maxn << 1];
int nxt3[maxn << 1], to3[maxn << 1], head3[maxn], val3[maxn << 1];
int dis[maxn], dis2[maxn];
int tot1, tot2, tot3;
bool vis1[maxn << 1], vis2[maxn << 1];
inline void add1(int a, int b, int c) {
to[++tot1] = b;
val[tot1] = c;
nxt[tot1] = head[a];
head[a] = tot1;
}
inline void add2(int a, int b, int c) {
to2[++tot2] = b;
val2[tot2] = c;
nxt2[tot2] = head2[a];
head2[a] = tot2;
}
inline void add3(int a, int b, int c) {
to3[++tot3] = b;
val3[tot3] = c;
nxt3[tot3] = head3[a];
head3[a] = tot3;
}
inline void dijsktra() {
memset(dis, inf, sizeof(dis));
memset(vis1, 0, sizeof(vis1));
dis[1] = 0;
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int> >, std::greater<std::pair<int, int> > > q;
q.push(std::make_pair(0, 1));
while (!q.empty()) {
int u = q.top().second;
q.pop();
if (vis1[u]) continue;
vis1[u] = 1;
for (int i = head[u]; i; i = nxt[i]) {
int v = to[i];
//if (vis[v]) continue;
if (dis[v] > dis[u] + val[i]) {
dis[v] = dis[u] + val[i];
q.push(std::make_pair(dis[v], v));
}
}
}
}
inline void oppodijsktra() {
memset(dis2, inf, sizeof(dis2));
memset(vis2, 0, sizeof(vis2));
dis2[n] = 0;
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int> >, std::greater<std::pair<int, int> > > q2;
q2.push(std::make_pair(0, n));
while (!q2.empty()) {
int u = q2.top().second;
q2.pop();
if (vis2[u]) continue;
vis2[u] = 1;
for (int i = head2[u]; i; i = nxt2[i]) {
int v = to2[i];
//if (vis[v]) continue;
if (dis2[v] > dis2[u] + val2[i]) {
dis2[v] = dis2[u] + val2[i];
q2.push(std::make_pair(dis2[v], v));
}
}
}
}
int dfn[maxn], low[maxn];
bool vis[maxn], isZero[maxn];//isZero判断是否为零环上点
int cnt;
std::stack<int> st;
inline void tarjan(int x) {
dfn[x] = low[x] = ++cnt;
st.push(x);
vis[x] = 1;
for (int i = head3[x]; i; i = nxt3[i]) {
int v = to3[i];
if (!dfn[v]) {
tarjan(v);
low[x] = std::min(low[x], low[v]);
} else if (vis[v]) {
low[x] = std::min(low[x], dfn[v]);
}
}
if (dfn[x] == low[x]) {
int sum = 0, temp;
do {
temp = st.top();
st.pop();
vis[temp] = 0;
sum++;
} while (temp != x);
isZero[x] = 1;
if (sum == 1) isZero[x] = 0;//0点(且已知不存在自环)不属于零环
}
}
bool flag = 0;
int dp[maxn][55];
int dfs(int x, int k) {
if (dp[x][k] != -1) return dp[x][k];
dp[x][k] = 0;
for (int i = head2[x]; i; i = nxt2[i]) {
int v = to2[i], w = val2[i];
int temp = dis[x] + k - (dis[v] + w);
if (temp < 0) continue;
dp[x][k] = (dp[x][k] + dfs(v, temp)) % p;
}
return dp[x][k];
}
inline void clear() {
memset(nxt, 0, sizeof(nxt));
memset(to, 0, sizeof(to));
memset(val, 0, sizeof(val));
memset(nxt2, 0, sizeof(nxt2));
memset(to2, 0, sizeof(to2));
memset(val2, 0, sizeof(val2));
memset(nxt3, 0, sizeof(nxt3));
memset(to3, 0, sizeof(to3));
memset(val3, 0, sizeof(val3));
for (int i = 1; i <= n; i++)
head[i] = head2[i] = head3[i] = low[i] = dfn[i] = isZero[i] = vis[i] = 0;
ans = flag = 0;
tot1 = tot2 = tot3 = 0;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d%d%d", &n, &m, &k, &p);
clear();
while (m--) {
scanf("%d%d%d", &X, &Y, &Z);
add1(X, Y, Z);
add2(Y, X, Z);
if (Z == 0) add3(X, Y, Z);
}
dijsktra();
oppodijsktra();
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i);
for (int i = 1; i <= n; i++) {
if (isZero[i]) {
if (dis[i] + dis2[i] <= dis[n] + k) {
flag = 1;
break;
}
}
}
if (flag) {
printf("-1\n");
continue;
}//判断会产生-1解的0环
memset(dp, -1, sizeof(dp));
dp[1][0] = 1;
for (int i = 0; i <= k; i++)
ans = (ans + dfs(n, i)) % p;
printf("%d\n", ans);
}
return 0;
}
参考:
[1].^零环与无穷多解关系的证明与思路
标签:NOIP2017,std,dist,int,memset,hack,sizeof,dp From: https://www.cnblogs.com/MrWangnacl/p/16861743.html