题目描述
策策同学特别喜欢逛公园。公园可以看成一张 N N 个点条边构成的有向图,且没有 自环和重边。其中1号点是公园的入口, N N 号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从1号点进去,从号点出来。
策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果1号点 到
N
N
号点的最短路长为,那么策策只会喜欢长度不超过
d+K
d
+
K
的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?
为避免输出过大,答案对
P
P
取模。
如果有无穷多条合法的路线,请输出−1。
输入格式:
第一行包含一个整数 , 代表数据组数。
接下来
T
T
组数据,对于每组数据: 第一行包含四个整数 ,每两个整数之间用一个空格隔开。
接下来
M
M
行,每行三个整数
代表编号为
ai,bi
a
i
,
b
i
的点之间有一条权值为
ci
c
i
的有向边,每两个整数之间用一个空格隔开。
输出格式:
输出文件包含
T
T
行,每行一个整数代表答案。
题目
解:
看看 k k 小于50对吧?自然而然想到了拆点。不过怎么拆点就很尴尬了。经过冥思苦想想到一种做法:跑一遍最短路然后把每个点拆成个 f[i][j] f [ i ] [ j ] 表示以 i i 为起点当前路径长度为的状态,里面存从这个点到终点的路径长度,如果没有零权环的话,可以证明这个图是一个dag。
然后就是记忆化搜索找路径条数,这个大家都会。现在你已经得了70分了,然后就是判零权环:注意,不一定图中有零权环就一定无数解,一定要环在路径上才行。我这里直接暴力判环。每个在路径中的点dfs,只走边权为零的边,要是走到自己,则无数解。昨天没有想到,看了别人的题解发现有更简单的判法:求拓扑序(不用dfs写法),如果无法把终点加入拓扑序中则证明路径上有零权环。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct lxy{
int next,to,len;
}b[200005];
int head[100005];
int cnt=0,T,k,m,n,p;
int f[100005][55];
bool vis[100005];
int dis[100005];
bool ris[100005][55];
bool col[100005];
int hhh=0;
int aim;
void add(int op,int ed,int len)
{
cnt++;
b[cnt].next=head[op];
head[op]=cnt;
b[cnt].to=ed;
b[cnt].len=len;
}
inline void Read(int &a) {//梁dalao的读入优化
char c=getchar();
a=0;
while(c>'9'||c<'0')
c=getchar();
while(c<='9'&&c>='0') {
a*=10;
a+=c-'0';
c=getchar();
}
return;
}
void spfa()//求最短路
{
queue <int> d;
dis[1]=0;
d.push(1);
vis[1]=1;
while(!d.empty())
{
register int noww=d.front();
vis[noww]=0;
d.pop();
for(register int i=head[noww];i!=-1;i=b[i].next)
{
if(dis[b[i].to]>dis[noww]+b[i].len)
{
dis[b[i].to]=dis[noww]+b[i].len;
if(vis[b[i].to]==0)
{
vis[b[i].to]=1;
d.push(b[i].to);
}
}
}
}
}
int dfs(int u,int r)//数路径条数
{
if(ris[u][r]==1)
return f[u][r];
ris[u][r]=1;
if(u==n)//如果到终点,有一条路到终点
f[u][r]=1;
register int y;
for(register int i=head[u];i!=-1;i=b[i].next)
{
if(dis[u]+r+b[i].len<=dis[b[i].to]+k)//如果到下个点的路径长不超过下一个点的最短路+k,可以向下走
{
y=dis[u]+r+b[i].len-dis[b[i].to];
f[u][r]=(f[u][r]+dfs(b[i].to,y))%p;
}
}
if(f[u][r]!=0)//如果从这个点可以到终点,标记这个点在路径里
col[u]=1;
return f[u][r];
}
void pan(register int u)//判断零权环
{
if(hhh==1)return;
if(vis[u]==1)return;
vis[u]=1;
for(register int i=head[u];i!=-1;i=b[i].next)
if(b[i].len==0)//只走零权边
{
if(b[i].to==aim)//走到自己答案标记为无穷解
hhh=1;
else
pan(b[i].to);
}
}
int main()
{
scanf("%d",&T);
while(T!=0)
{
T--;
scanf("%d%d%d%d",&n,&m,&k,&p);
cnt=0;
memset(f,0,sizeof(f));
memset(head,-1,sizeof(head));
memset(dis,0x7f,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(ris,0,sizeof(ris));
memset(b,0,sizeof(b));
memset(col,0,sizeof(col));
hhh=0;
for(register int i=1;i<=m;i++)
{
register int x,y,z;
Read(x);
Read(y);
Read(z);
add(x,y,z);
}
spfa();
register int ans=dfs(1,0);
for(register int i=1;i<=n;i++)//对于每个路径中的点都要判断能否走到自己
if(col[i]==1)
{
memset(vis,0,sizeof(vis));
aim=i;
pan(i);
if(hhh==1)
break;
}
if(hhh==1)
printf("-1\n");
else
printf("%d\n",ans%p);
}
}
复杂度十分玄学,其实有点爆炸,加读入优化勉强卡进2.5秒