洛谷P3953 逛公园
题目描述
策策同学特别喜欢逛公园。公园可以看成一张\(N\)个点\(M\)条边构成的有向图,且没有自环和重边。其中1号点是公园的入口,\(N\)号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从1号点进去,从\(N\)号点出来。
策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果1号点 到\(N\)号点的最短路长为\(d\),那么策策只会喜欢长度不超过\(d+K\)的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?
为避免输出过大,答案对\(P\)取模。
如果有无穷多条合法的路线,请输出\(-1\)。
输入输出格式
输入格式:
第一行包含一个整数 \(T\), 代表数据组数。
接下来\(T\)组数据,对于每组数据: 第一行包含四个整数 \(N,M,K,P\),每两个整数之间用一个空格隔开。
接下来\(M\)行,每行三个整数\(a_i,b_i,c_i\),代表编号为\(a_i,b_i\)的点之间有一条权值为 \(c_i\)的有向边,每两个整数之间用一个空格隔开。
输出格式:
输出文件包含 \(T\) 行,每行一个整数代表答案。
思路
记忆化搜索
首先从\(N\)开始反向求一遍最短路,这样就得到了每一个点到\(N\)号节点的最短路径长度,记\(Dis_i\)为\(i\)号节点到\(N\)号节点的最短路径长度。
\(F_{i,j}\)表示第\(i\)号节点在路线长度为\(d+k-j\)时的路线条数,或者说是第\(i\)号节点还可以浪费\(j\)个单位距离时的路线条数。
那么转移方程如下:\[F_{i,j}=\sum_{k \in \{ k| (i,k) \in E \}}{F_{k,j+Dis_i-(Dis_k+W_{i,k})}}\]
因此,本问题也就可以用记忆化搜索求了
再考虑有无数解的情况,很显然,当且仅当出现零环的时候回有无数解。
也就是说,我们只要在记忆化搜索之前找一下有没有零环,有的话直接输出\(-1\)没有的话在做记忆化搜索
可以用tarjan缩点判断零环
先将图中所有长度大于\(0\)的边都去掉,然后跑tarjan,如果缩出来的强连通分量中有顶点数大于\(1\)的,那么就说明有零环。
CODE
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
#define MAXN 100010
#define MAXM 200010
struct R{
int id,dis;
R(){}
R(int id,int dis):id(id),dis(dis){}
bool operator < (const R &a) const {
return dis>a.dis;
}
};
struct Node{
int u,v,w;
Node(){}
Node(int u,int v,int w):u(u),v(v),w(w){};
}p[MAXM],revp[MAXM];
int head[MAXN],revhead[MAXN],Next[MAXM],revNext[MAXM],dis[MAXN],cnt[MAXN][55],dfn[MAXN],low[MAXN],color[MAXN];
bool vis[MAXN];
int i,j,k,m,n,x,y,u,v,w,tot,revtot,times,colnum,t,r,mod,mindis;
stack<int> mstack;
char readc;
bool flag;
void read(int &n){
while((readc=getchar())<48||readc>57);
n=readc-48;
while((readc=getchar())>=48&&readc<=57) n=n*10+readc-48;
}
void addNode(int u,int v,int w){
p[++tot]=Node(u,v,w);
Next[tot]=head[u],head[u]=tot;
revp[++revtot]=Node(v,u,w);
revNext[revtot]=revhead[v],revhead[v]=revtot;
}
bool relax(int u,int v,int w){
if(dis[u]+w<dis[v]){
dis[v]=dis[u]+w;
return true;
}
return false;
}
void tarjan(int src){
low[src]=dfn[src]=++times;
vis[src]=true;
mstack.push(src);
for(int i=head[src];i+1;i=Next[i]){
if(p[i].w) continue;
if(!dfn[p[i].v]){
tarjan(p[i].v);
low[src]=min(low[src],low[p[i].v]);
}else{
if(vis[p[i].v]) low[src]=min(low[src],dfn[p[i].v]);
}
}
if(low[src]==dfn[src]){
int tmp;
colnum++;
do{
tmp=mstack.top(); mstack.pop();
vis[tmp]=false;
color[colnum]++;
}while(tmp!=src);
}
}
void dijkstra(int src){
memset(dis,0x3f,sizeof(dis));
priority_queue<R> mque;
dis[src]=0;
mque.push(R(src,0));
while(!mque.empty()){
R tmp=mque.top(); mque.pop();
if(tmp.dis>dis[tmp.id]) continue;
for(int i=revhead[tmp.id];i+1;i=revNext[i])
if(relax(revp[i].u,revp[i].v,revp[i].w)){
mque.push(R(revp[i].v,dis[revp[i].v]));
}
}
}
int dfs(int src,int cost){
if(cnt[src][cost]+1) return cnt[src][cost];
cnt[src][cost]=0;
if(src==n) cnt[src][cost]++;
for(int i=head[src];i+1;i=Next[i]){
if((p[i].w+dis[p[i].v])-dis[src]<=cost){
cnt[src][cost]+=dfs(p[i].v,cost+dis[src]-p[i].w-dis[p[i].v]);
cnt[src][cost]%=mod;
}
}
return cnt[src][cost];
}
int main(){
read(t);
for(r=1;r<=t;r++){
memset(head,-1,sizeof(head));
memset(revhead,-1,sizeof(revhead));
memset(vis,0,sizeof(vis));
memset(color,0,sizeof(color));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(cnt,-1,sizeof(cnt));
tot=revtot=-1;
times=colnum=0;
read(n),read(m),read(k),read(mod);
for(i=1;i<=m;i++){
read(u),read(v),read(w);
addNode(u,v,w);
}
for(i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
flag=false;
for(i=1;i<=colnum;i++){
if(color[i]>1){
flag=true;
break;
}
}
if(flag){
printf("-1\n");
continue;
}
dijkstra(n);
mindis=dis[1];
printf("%d\n",dfs(1,k));
}
return 0;
}