P3953 逛公园
欢迎hack!毕竟这题被hack的人太多了,我也有可能出错,希望大家带着批判的眼光看这篇题解。
看完题就应该发现那个 \(k\) 特别小,显然可以利用
关于 \(-1\) ,其实不是很好处理。非常显然我们需要判断的是“是否在一条满足题意(可以从 \(1\) 到 \(n\))的路径上存在 \(0\) 环”,很多错解都是因为对于“满足题意的路径”限制方法出错然后挂掉的。
考虑对于每个节点开一个 \(dp[i][j]\) 表示:从这个点开始走,比最短路多走了 \(j\) 的长度的方案数
可以先一遍 \(Dijsktra\) 跑出从 \(1\) 到每个点的最短路 \(dis[i]\)
考虑从 \(1\) 开始记搜出 \(dp[1][i]\) 就是答案
其实原本是可以直接dp的,但是记搜过程可以顺便判 \(0\) 环,不然还得再写一个 \(dfs\) 判环。更重要的是,我们要找的是“可以到达的点上是否存在 \(0\) 环”,那么在记搜过程中的体现就是访问到了这个点,省去一些判断,也可以少访问一些节点。
-
判 \(0\) 环:对于这个状态是否在栈中搜索开个
bool
记录一下,如果访问到状态 \(i,j\) 并且 \(ins[i][j]\) 为真的情况那么必然存在 \(0\) 环,因为它绕了一圈但是比最短路多的长度还是 \(j\) 。碰到 \(0\) 环直接退出即可 -
\(dp\):考虑从 \(u\) 走到 \(v\) 会多产生多少贡献。原本走到 \(v\) 是 \(dis[v]\) 的长度,现在是 \(dis[u]+w[i]\) ,多走了 \(dis[u]+w[i]-dis[v]\) ,那么还可以多走 \(lft-(dis[u]+w[i]-dis[v])\) ,即 \(dp[u][lft]\to dp[v][lft-(dis[u]+w[i]-dis[v])]\)
我就是这么写的,然而在洛谷被 hack 了,CCF的数据挺水的,就水过去了 所以这是我在考场上能AC的意思?
hack数据长这样:
Input
1
4 5 0 10000
1 2 10
2 1 10
2 3 0
3 2 0
1 4 100
Output
1
像我那样写会被hack的原因在于我对于能到达的限制出现了问题,数据构造了一个死胡同 \(1\to2\to3\) ,并且 \(2\to 3\to 2\) 是 \(0\) 环,但是并不能到 \(n\) 。
接下去考虑解决这个漏洞。
发现只要在反图上跑就能避免这个问题了,即从 \(n\) 跑到 \(1\) ,因为我们最短路是从 \(1\) 开始记录的,如果在 \(n\) 到 \(1\) 的路径上出了个“岔子”或“死胡同”,最短路一定不会往那里延伸,那么就能完美地解决这个问题了!
注意 dp 方程也要相应改变。
话说我这代码怎么跑这么快qwq,最优解第三,没卡过常呢,正常写的代码。
upd:卡完常变慢了/kk
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return f?x:-x;
}
#define N 100005
#define M 200005
#define K 55
int n,m,k,mod,ans,flg,dis[N],dp[N][K];
bool vis[N],ins[N][K];
void fmod(int&x){x-=mod,x+=x>>31&mod;}
struct edge{
int nxt,to,val;
}e[M],E[M];
int head[N],num_edge,Head[N],Num_edge;
void addedge(int fr,int to,int val){
++num_edge;
e[num_edge].nxt=head[fr];
e[num_edge].val=val;
e[num_edge].to=to;
head[fr]=num_edge;
}
void Addedge(int fr,int to,int val){
++Num_edge;
E[Num_edge].nxt=Head[fr];
E[Num_edge].val=val;
E[Num_edge].to=to;
Head[fr]=Num_edge;
}
void clear(){
ans=0;
memset(dp,-1,sizeof(dp));
memset(head,0,sizeof(head));
memset(Head,0,sizeof(Head));
num_edge=Num_edge=0,flg=1;
}
struct Dij{
int u,dis;
Dij(int u_,int d_){u=u_,dis=d_;}
inline bool operator < (const Dij&t)const{return dis>t.dis;}
};
priority_queue<Dij>q;
void dij(){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
q.push(Dij(1,dis[1]=0));
while(!q.empty()){
Dij now=q.top();q.pop();
int u=now.u;
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(dis[v]>dis[u]+e[i].val){
dis[v]=dis[u]+e[i].val;
if(!vis[v])q.push(Dij(v,dis[v]));
}
}
}
}
int dfs(int u,int lft){
if(!flg)return 0;
if(ins[u][lft])return flg=0;
if(~dp[u][lft])return dp[u][lft];
int res=0;
ins[u][lft]=1;
for(int i=Head[u];i&&flg;i=E[i].nxt){
int v=E[i].to;
int w=lft-(E[i].val-(dis[u]-dis[v]));
if(w>=0)fmod(res+=dfs(v,w));
}
ins[u][lft]=0;
if(u==1&&!lft)++res;
return dp[u][lft]=res;
}
void Main(){
clear();
n=read(),m=read(),k=read(),mod=read();
for(int i=1;i<=m;++i){
int x=read(),y=read(),z=read();
addedge(x,y,z),Addedge(y,x,z);
}
dij();
for(int i=0;flg&&i<=k;++i)fmod(ans+=dfs(n,i));
flg?printf("%d\n",ans):puts("-1");
}
signed main(){for(int T=read();T;--T)Main();}