题解 P3953 【逛公园】
本人观察了大部分题解,发现真正能通过目前为止所有hack数据的不足5篇(所以也建议管理员撤下部分明显错误的题解,比如找到0环就-1的)
本人加以总结归纳,得出了可以通过到目前为止所有hack数据的做法(直接讲满分做法了):
考虑到有无穷多中方案,当且仅当1->n的一条可行路径上存在一个0环.
如何找真正意义上的0环?先标记所有连有0边的
最后,满足
判断
综上,当且仅当存在同时满足上述两个条件的点,就有无穷多条合法路径,即需要输出-1.
接下来就是常规做法了:
f[j][u]表示从1到u,距离为dis1[u]+j的路径数
转移时,先以
理论时间复杂度
为了让更多人看到正解,也为了我2h写正解代码,1h写这篇题解,点个赞再看代码吧.
没错,210行,去掉库约160行.不过我刻意没有压行
//Wan Hong 3.0
//notebook
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
#include <stack>
#include <set>
#include <map>
typedef unsigned un;
typedef std::string str;
typedef int ll;
typedef std::pair<ll,ll> pll;
const ll INF=0x3f3f3f3f;
ll read()
{
ll x=0,f=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return f*x;
}
ll min(ll a,ll b)
{
return a<b?a:b;
}
ll max(ll a,ll b)
{
return a>b?a:b;
}
bool umin(ll &a,ll b)
{
if(b<a)return a=b,1;
return 0;
}
bool umax(ll &a,ll b)
{
if(b>a)return a=b,1;
return 0;
}
ll abs(ll x)
{
return x>0?x:-x;
}
/**********/
#define MAXN 100011
#define MAXM 200011
#define MAXK 51
struct Edge//邻接表存图
{
ll v,w,nxt;
}e[MAXM];
ll cnt=0,last[MAXN];
void adde(ll u,ll v,ll w)//加边
{
++cnt;
e[cnt].v=v,e[cnt].w=w;
e[cnt].nxt=last[u],last[u]=cnt;
}
ll cu[MAXM],cv[MAXM],cw[MAXM];//拷贝边,重建图用
struct node//Dij的优先队列节点
{
ll u,dis;
bool operator <(const node& t)
const
{
return dis>t.dis;
}
node(ll _u=0,ll _dis=0)
{
u=_u,dis=_dis;
}
};
ll dis1[MAXN],disn[MAXN];
void Dij(ll s,ll dis[])//Dij求最短路
{
std::priority_queue<node>q;
dis[s]=0;q.push(node(s,dis[s]));
while(!q.empty())
{
ll u=q.top().u,tmp=q.top().dis;q.pop();
if(dis[u]!=tmp)continue;
for(ll i=last[u];i;i=e[i].nxt)
{
ll v=e[i].v;
if(umin(dis[v],dis[u]+e[i].w))
{
q.push(node(v,dis[v]));
}
}
}
}
ll ind[MAXN],tag[MAXN],ord[MAXN],vis1[MAXN],vis2[MAXN];//入度,是否有0边,拓扑序,是否不在环上
ll n,m,k,p;
void topo(ll vis[])//拓扑排序
{
ll now=0;
std::queue<ll>q;
for(ll i=1;i<=n;++i)
if(!ind[i]&&tag[i])q.push(i);
while(!q.empty())
{
ll u=q.front();q.pop();
vis[u]=1;ord[u]=++now;
for(ll i=last[u];i;i=e[i].nxt)
{
ll v=e[i].v;
if(!--ind[v])q.push(v);
}
}
}
bool cmp(ll x,ll y)
{
if(dis1[x]==dis1[y])return ord[x]<ord[y];
return dis1[x]<dis1[y];
}
ll f[MAXK][MAXN],a[MAXN];//dp数组和dp顺序
int main()
{
ll task=read();
while(task--)
{
memset(tag,0,sizeof tag);//清空全局变量
cnt=0;
memset(last,0,sizeof last);
memset(ord,0,sizeof ord);
memset(vis1,0,sizeof vis1);memset(vis2,0,sizeof vis2);
n=read(),m=read(),k=read(),p=read();
for(ll i=1;i<=m;++i)//建原图
{
cu[i]=read(),cv[i]=read(),cw[i]=read();
adde(cu[i],cv[i],cw[i]);
}
memset(dis1,0x3f,sizeof dis1);//两次Dij求最短路
Dij(1,dis1);
cnt=0;
memset(last,0,sizeof last);
for(ll i=1;i<=m;++i)
adde(cv[i],cu[i],cw[i]);
memset(disn,0x3f,sizeof disn);
Dij(n,disn);
cnt=0;//取出0边跑第一次拓扑排序
memset(last,0,sizeof last);
memset(ind,0,sizeof ind);
for(ll i=1;i<=m;++i)
if(cw[i]==0)
{
tag[cu[i]]=tag[cv[i]]=1;
++ind[cu[i]];
adde(cv[i],cu[i],cw[i]);
}
topo(vis1);
cnt=0;//取出0边跑第二次拓扑排序
memset(last,0,sizeof last);
memset(ind,0,sizeof ind);
for(ll i=1;i<=m;++i)
if(cw[i]==0)
{
++ind[cv[i]];
adde(cu[i],cv[i],cw[i]);
}
memset(ord,0,sizeof ord);
topo(vis2);
bool flag=0;
for(ll i=1;i<=n;++i)
if(tag[i]&&!vis1[i]&&!vis2[i]&&dis1[i]+disn[i]<=dis1[n]+k)//关键的判-1部分
{
flag=1;
break;
}
if(flag)
{
puts("-1");
continue;
}
cnt=0;
memset(last,0,sizeof last);//重建原图
for(ll i=1;i<=m;++i)
adde(cu[i],cv[i],cw[i]);
for(ll i=1;i<=n;++i)a[i]=i;
std::sort(a+1,a+n+1,cmp);
memset(f,0,sizeof f);
f[0][1]=1%p;
ll ans=0;
for(ll j=0;j<=k;++j)//dp计算路径
{
for(ll t=1;t<=n;++t)
{
ll u=a[t];
if(!f[j][u])continue;
for(ll i=last[u];i;i=e[i].nxt)
{
ll v=e[i].v,cur=dis1[u]+j+e[i].w-dis1[v];
if(cur<=k)f[cur][v]=(f[cur][v]+f[j][u])%p;
}
}
ans=(ans+f[j][n])%p;
}
printf("%d\n",ans);
}
return 0;
}