逛公园
题意:在一张有向图中,求出1到n有多少条路径长度不超过最短路+K。
30分做法:K=0时,就是最短路计数,详见P1144最短路计数
#include <queue>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <cmath>
#include <queue>
using namespace std;
const int maxm = 400007;
int pre[maxm], other[maxm], len[maxm], l, last[100007];
int cnt[100007], dis[100007];
bool vis[100007];
int t;
int n, m, k, mo;
void add(int x, int y, int z) {
l++;
pre[l] = last[x];
last[x] = l;
other[l] = y;
len[l] = z;
}
priority_queue<pair<int, int> > q;
void dijkstra() {
memset(dis, 63, sizeof(dis));
memset(vis, 0, sizeof(vis));
memset(cnt, 0, sizeof(cnt));
dis[1] = 0;
cnt[1] = 1;
q.push(make_pair(0, 1));
while (q.size()) {
int u = q.top().second;
q.pop();
if (vis[u])
continue;
vis[u] = 1;
for (int p = last[u]; p; p = pre[p]) {
int v = other[p];
if (dis[v] == dis[u] + len[p])
cnt[v] += cnt[u], cnt[v] %= mo;
if (dis[v] > dis[u] + len[p]) {
cnt[v] = cnt[u] % mo;
dis[v] = dis[u] + len[p];
q.push(make_pair(-dis[v], v));
}
}
}
}
int main() {
scanf("%d", &t);
while (t--) {
scanf("%d%d%d%d", &n, &m, &k, &mo);
l = 0;
for (int i = 1; i <= n; i++) last[i] = 0;
for (int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
}
if (k == 0) {
dijkstra();
cout << cnt[n] << endl;
}
}
return 0;
}
以下的dis[i]表示到1到i的最短路
满分做法:考虑DP,因为K<=50,很容易想到f[u][j]表示到u路径长度为dis[u]+j的路径数。f[u][j]一定由f[v][pos]转移过来,v是和u有连边的点(v指向u),pos=dis[i]+j-len[p]-dis[v];因为根据最短路性质,dis[u]<=dis[v]+len[p],dis[u]-dis[v]-leb[p]<=0,所以pos<=j。可以从小到大枚举n结点的j,这样就可以算出n之前所有结点的f值,再向n转移即可。如果pos<0,说明此时dis[v]+len[p]>dis[u]+j,无法转移。我的做法是建反边跑记忆化搜索,这样可以少搜索到一些无用的状态。
判-1的情况:一条合法路径上出现了零环。因为我们是根据F[n][j]搜索的,所以搜到的状态都是合法的,开一个bool数组记录此状态是否遍历过,因为出现零边的话,pos=j,绕一圈绕到一个被遍历的状态,说明就有零环。
此外,给1节点直接赋初值不是很好的选择,因为如果1号节点就处在零环中,他的f值就不是1了。对AC没有影响,但还是要理解。在此开一个虚拟节点,连接1号就行了。
#include <queue>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <cmath>
#include <queue>
using namespace std;
typedef long long ll;
const ll inf = 1e13;
const int maxm = 200007;
int pre[maxm], other[maxm], len[maxm], l, last[100007];
int pre1[maxm], other1[maxm], len1[maxm], l1, last1[100007];
ll dis[100007];
bool vis[100007], flag[100007][55], huan;
int t;
int n, m, k;
ll ans, f[100007][55], mo;
void add(int x, int y, int z) {
l++;
pre[l] = last[x];
last[x] = l;
other[l] = y;
len[l] = z;
}
void add1(int x, int y, int z) {
l1++;
pre1[l1] = last1[x];
last1[x] = l1;
other1[l1] = y;
len1[l1] = z;
}
priority_queue<pair<ll, int> > q;
void dijkstra() {
for (int i = 1; i <= n; i++) dis[i] = inf;
memset(vis, 0, sizeof(vis));
dis[1] = 0;
q.push(make_pair(0, 1));
while (q.size()) {
int u = q.top().second;
q.pop();
if (vis[u])
continue;
vis[u] = 1;
for (int p = last[u]; p; p = pre[p]) {
int v = other[p];
if (dis[v] > dis[u] + len[p]) {
dis[v] = dis[u] + len[p];
q.push(make_pair(-dis[v], v));
}
}
}
}
inline ll dfs(int x, int pos) {
if (f[x][pos] != -1)
return f[x][pos];
f[x][pos] = 0;
flag[x][pos] = 1;
for (int p = last1[x]; p; p = pre1[p]) {
int v = other1[p];
int s = pos + dis[x] - len1[p] - dis[v];
if (s < 0)
continue;
if (flag[v][s])
huan = 1;
f[x][pos] += dfs(v, s), f[x][pos] %= mo;
}
flag[x][pos] = 0;//不要忘了回溯,因为一条路径和另一条路径是两码事
return f[x][pos];
}
int main() {
scanf("%d", &t);
while (t--) {
scanf("%d%d%d%lld", &n, &m, &k, &mo);
l = 0;
l1 = 0;
huan = 0;
ans = 0;
for (int i = 1; i <= n + 1; i++) last[i] = 0, last1[i] = 0;
memset(flag, 0, sizeof(flag));
memset(f, -1, sizeof(f));
for (int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
add1(y, x, z);
}
dijkstra();
add1(1, n + 1, 0);
f[n + 1][0] = 1;
dis[n + 1] = 0;
for (int i = 0; i <= k; i++) ans = (ans + dfs(n, i)) % mo;
if (huan)
cout << "-1" << endl;
else
printf("%lld
", ans);
}
return 0;
}