简析
看到题第一反应用最短路计数的做法骗分(2333333)
总之我们来看看部分分的做法。
(30pts)
(K = 0) ,可以狂喜了。
由于 (spfa) 比较好写,所以在这里仅仅给出需要在模板上进行修改的地方,其他细节请读者自行思考:
if(u == n) continue;
......
if(d[v] == d[u] + w){
f[v] += f[u];
}else if(d[v] > d[u] + w){
d[v] = d[u] + w;
f[v] = f[u];
}if(!vis[v] && f[v]) q.push(v),vis[v] = 1;
......
f[u] = 0;//出队时清掉f
我们依次分析一下以上代码:
当扩展到终点时,我们应当直接跳过他(注意不是终止函数),用终点再去扩展终点的出边可能会导致答案重复统计。
显然,当从 (u) 到 (v) 的权与 (u) 当前最短路的和与当前 (v) 的最短路相等时,我们将 (v) 点的方案数加上 (u) 点的方案数。
如果最短路被更新,那么 (v) 原有的方案全部作废,新方案数与 (u) 方案数相同。
为什么入队有两个判断条件呢?第一个条件是显然的,本来就在队列中的点不应重复入队。对于第二个条件,显然只有进入了以上两个分支, (f[v]) 才会被更新。该条件保证了不扩展冗余的节点。
由于 (spfa) 的原理,每个节点可能入队多次,如果不清空 (f) 数组可能导致答案的重复统计,因此在离队时要清掉。
一个小小的题外话
关于 (spfa) ,我们发现由于其有着节点可能会多次进队的特殊性质,因此在运用它不论是进行什么样的操作,尤其是递推式更新时,一定要注意重复扩展的问题,或者换用 (dijkstra) 算法司普发早死几百年了。
对于本题 (30pts),由于 (dijkstra) 算法改动部分大同小异,因此这里不再赘述,仅保留以上添加代码的第二部分并将其改为 (dijkstra) 风格即可。
(70pts)
考虑没有 (0) 边的情况,由于 (K = 50) 我们来考虑一下 (dp) 做法。
如何设计状态呢?一个显而易见的思路如下:
我们现在已经计算出了到 (n) 点的最短路 (d[n]),显然题设要求路径长度不得超过 (d[n] + K)。不妨令 (f[i][j]) 表示在 (i) 号点,剩下可以走的路的长度是 (j)。
初态 : (f[1][d[n] + k] = 1;)
末态 : (ans += f[n][j];) (j) :(d[n] -> d[n] + k)
转移 : (E(u,v,w)),(f[v][j - w] += f[u][j];)
但 (d[n]) 可能很大,开不下,怎么办?
注意到我们有从 (1) 到每个点的最短路径长。同时观察终态我们可以发现统计答案时只用到了 (d[n] -> d[n] + k)。那么考虑在第二维只记录 (K)。(f[i][j]) 表示在 (i) 号点,剩下可以多走的路的长度是 (j)。很显然,我们只需要在走的过程中逐步计算相对于最短路多走的路径长,保证这个多出来的长小于 (K) 即可。
不过,这个做法的正确性似乎有待商榷。我们知道全局最短路并不完全是局部最短路,从 (u) 到 (v) 的最优路径也不一定是 (d[v] + d[u]) ,但这种思路处理本题足矣。
希望有想法的读者能在评论区留下您的看法,对此法证明或证伪,鄙人感激不尽。
(100pts)
考虑满分做法。当 (0) 边存在时,显然只有出现 (0) 环时无解。我们对递归中的状态打上一个标记,一旦发现尚在递归中的状态访问到自身时(访问同一节点且剩余路长相等)则返回无解。
AC代码
#include<iostream>
#include<cctype>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 100005;
int read(){
int re = 0,ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) re = (re<<1) + (re<<3) + ch - '0',ch = getchar();
return re;
}
struct edge{
int v,w,nxt;
}e[maxn<<1];
int h[maxn],cnt;
void add(int u,int v,int w){
e[++cnt].w = w;
e[cnt].v = v;
e[cnt].nxt = h[u];
h[u] = cnt;
}
int n,m,k,p,t;
bool vis[maxn];
int dis[maxn];
int f[maxn][55];
bool v[maxn][55];
void spfa(){
memset(dis,0x3f,sizeof(dis));
queue<int> q;
vis[1] = 1;
dis[1] = 0;
q.push(1);
while(q.size()){
int u = q.front();q.pop();
vis[u] = 0;
for(int i = h[u];i;i = e[i].nxt){
int v = e[i].v,w = e[i].w;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
if(!vis[v]) q.push(v),vis[v] = 1;
}
}
}
}
int dfs(int u,int left){
if(left < 0) return 0;
if(v[u][left]) return -1;// 0 ring
if(f[u][left] != -1) return f[u][left];
v[u][left] = 1;
int ans = 0;
if(u == n) ans++;
for(int i = h[u];i;i = e[i].nxt){
int v = e[i].v,w = e[i].w;
int ansc = dfs(v,left - (dis[u] + w - dis[v]));
if(ansc == -1) return -1;
ans = (ansc + ans) % p;
}
f[u][left] = ans % p;
v[u][left] = 0;
return ans;
}
int main(){
t = read();
while(t--){
n = read(),m = read(),k = read(),p = read();
memset(h,0,sizeof(h));
memset(f,-1,sizeof(f));
memset(v,0,sizeof(v));
cnt = 0;
for(int i = 1;i <= m;i++){
int u = read(),v = read(),w = read();
add(u,v,w);
}
spfa();
printf("%d
",dfs(1,k));
}
return 0;
}