在WA了接近20遍后,本蒟蒻终于切掉了逛公园。。
逛公园的题意为求出图中从1到n路径长度<=d(最短路)+k 的
路径条数。
这道题十分的像luogu 1608 路径统计,虽然算法完全不一样,
但思想却十分相似。由于k十分的小,我们可以考虑枚举k,设
f[i][j] 为从 1 到 i 比最短路大j的路径的条数。则最终的答案就是
(f[n][0]+f[n][1]+…+f[n][k]) 如何求出发f[i][j]呢?可以从出发点开始dp,
但dp的状态似乎十分难表示,我们可以从终点开始倒
着进行记忆化这时我们会发现每次要扩展的状态特别
容易表示。我们可以用spfa 进行预处理,用w[i]表
示从1到i的最短路。我们先来看一个式子:
w[u]-(w[v]+e[u].val)+j (以下u,v都是反向建图后的:u代表起点,v代表终点,e[u].val代表边权)这句话的
含义为: (w[v]+e[u].val)-w[u]代表了如果选择由u转移
至v超过了 u–v 之间最短路的值,则:
f[e[i].to][j-w[u]-(w[v]+e[u].val)]就是u转移到v后符合要
求的路径的条数 (相当于从u转移到v消耗了多少k)
如果 j-w[u]-(w[v]+e[u].val) 为负数,就证明当前情况下
u无法转移到v。排除这种情况后,我们就可以递归求f[u][j]了,f[u][j]=sigma f[e[i].to][j-w[u]-(w[v]+e[u].val)];
#### 这种写法仍然会RE,我们需要记下状态,当重复搜到
#### 该状态时,直接return就可以了。
# 附上代码:
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
#include<string>
#include<stack>
using namespace std;
stack<int>st;
vector<int> pic[100001];
const int maxn=100001;
struct hzw{
int to;
int next;
int v;
}e[200001];
int cur,head[200001],w[maxn];
bool pan[maxn],ss[maxn];
int dfn[maxn],low[maxn],num[maxn];
int dfn2[maxn],low2[maxn],ss2[maxn];
bool v[100001];
int add(int a,int b,int c){
e[cur].to=b;
e[cur].next=head[a];
e[cur].v=c;
head[a]=cur++;
}
int head2[maxn*2],cur2;
struct ak{
int to2;
int next2;
int v2;
}e2[maxn*2];
int add2(int a,int b,int c){
e2[cur2].to2=b;
e2[cur2].next2=head2[a];
e2[cur2].v2=c;
head2[a]=cur2++;
}
int n,m,k,p,d;
bool end;
void spfa(int s){
bool vis[100001];
memset (vis,0,sizeof (vis));
memset (w,0x3f,sizeof (w));
queue<int>q;
q.push(s);
w[s]=0;
vis[s]=true;
while (!q.empty()){
int u=q.front();
q.pop();
vis[u]=false;
for (int i=head[u];i!=-1;i=e[i].next){
int a=e[i].to;
int b=e[i].v;
if (w[a]>w[u]+b){
w[a]=w[u]+b;
if (!(vis[a])){
q.push(a);
vis[a]=true;
}
}
}
}
}
void tarjan(int s) {//本人蠢笨,用tarjan判断的0环。
//其实开一个数组在dfs中记录状态的访问情况,
//如果在一次dfs中一种状态被再次访问,就可以直接判断0环了。
dfn[s]=++cur;
low[s]=cur;
st.push(s);
ss[s]=1;
int tm=pic[s].size();
for (int i=0; i<tm; ++i) {
int a=pic[s][i];
if (!dfn[a]){
tarjan(a);
low[s]=min(low[s],low[a]);
}
else if (ss[a]){
low[s]=min(low[s],dfn[a]);
}
}
if (dfn[s]==low[s]) {
int check=0;
int tmp=st.top();
while (tmp!=s) {
check=true;
ss[tmp]=0;
if (w[tmp]<=d+k) end=true;
st.pop();
tmp=st.top();
}
if (check){
tmp=st.top();
if (w[tmp]<=d+k) end=true;
ss[tmp]=0;
st.pop();
}
else{
tmp=st.top();
ss[tmp]=0;
st.pop();
}
}
}
long long one;
long long f[maxn][55],vis[maxn][55];
inline long long dfs(int u,int kk){
if (vis[u][kk]) return f[u][kk];
vis[u][kk]=true;
for (int i=head2[u];i!=-1;i=e2[i].next2) {
long long sum=( w[u]-(w[e2[i].to2]+e2[i].v2) )+kk;
if (sum<0) continue;
f[u][kk]=(f[u][kk]+dfs(e2[i].to2,sum))%p;
// 递归求出合法的路径条数
}
return f[u][kk];
}
int main(){
// freopen ("park9.in","r",stdin);
// freopen ("park.out","w",stdout);
int t;
cin>>t;
for (int sss=1;sss<=t;++sss){
memset (head,-1,sizeof (head));
memset(head2,-1,sizeof(head2));
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(pic,0,sizeof(pic));
memset(f,0,sizeof(f));
memset(vis,0,sizeof(vis));
one=0;
end=0;
cur=0;
cur2=0;
memset (w,0,sizeof (w));
cur=0;
cin>>n>>m>>k>>p;
int cnt=0;
for (int i=1;i<=m;++i){
int xx,yy,zz;
scanf("%d%d%d",&xx,&yy,&zz);
if (zz==0) {
pic[xx].push_back(yy);
num[++cnt]=xx;
}
add(xx,yy,zz);
add2(yy,xx,zz);
}
spfa (1);
for (int i=1;i<=cnt;++i){
cur=0;
if (!dfn[num[i]]) tarjan(num[i]);
}
if (end){
cout<<"-1"<<endl;
continue;
}
else {
long long ans=0;
f[1][0]=1;
for (int i=0;i<=k;++i) {
//枚举k
ans+=dfs(n,i);
ans%=p;
}
cout<<ans<<endl;
}
}
return 0;
}