P3953 [NOIP2017 提高组] 逛公园 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题用一种类似于背包问题中凑数的思想。
我们就是想要凑出有多少个是长度是d,d+1,d+2,....
那么我们首先使用dijkstra()算法算出最短路。
在解决这题的时候,笔者担心想要凑出的值k是一个负数,但是发现这样的想法是多余的。
怎样去寻找这样的路径?
或者这样的路径有怎样美妙的比喻?
因为我们转移的时候,是从距离终点最近的开始转移,所以需要存储反向边。
小心零环,因为触碰到零环就会导致方案数量是无穷。
类似于tarjan算法,我们判断是否有0环的方法,就是查找转移一个状态是否正在被查找。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int >PII;
#define x first
#define y second
const int inf=0x3f3f3f3f;
const int N=100010,M=5*N;
int rh[N],h[N],e[M],w[M],ne[M],idx;
int f[N][100];
int ins[N][100];
int dist[N],st[N];
int fail;
int n,m,P,K;
void add(int h[],int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
void dijkstra()
{
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII>>heap;
heap.push({0,1});
while(heap.size())
{
auto t=heap.top();
heap.pop();
int ver=t.y;
if(st[ver])continue;
st[ver]=true;
for(int i=h[ver];~i;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[ver]+w[i])
{
dist[j]=dist[ver]+w[i];
heap.push({dist[j],j});
}
}
}
}
//1
//5 7 2 10
//1 2 1
//2 4 0
//4 5 2
//2 3 2
//3 4 1
//3 5 2
//1 5 3
int dp(int u,int k)
{
if(k<0)return 0;
if(fail)return 0;//如果发现0环,返回
//cout<<"in:"<<u<<" "<<k<<"\n";
if(f[u][k]){
return f[u][k];}//如果已经记住了,直接返回。
int ans=0;
for(int i=rh[u];~i;i=ne[i])//从终点到节点
{
int j=e[i];
if(dist[u]+k-w[i]-dist[j]<0)continue;//如果方案比最优解要小,这是不可能的。
if(ins[j][dist[u]+k-w[i]-dist[j]])//如果已经在栈中
{
fail=1;//设置为失败
return 0;
}
ins[j][dist[u]+k-w[i]-dist[j]]=true;//设置在栈中
ans=(ans+dp(j,dist[u]+k-w[i]-dist[j]))%P;//动态转移
ins[j][dist[u]+k-w[i]-dist[j]]=false;//撤销
}
//cout<<"out:"<<u<<" "<<k<<"\n";
return f[u][k]=ans;//更新,返回
}
void solve()
{
//清空
fail=0;
idx=0;
memset(h,-1,sizeof h);
memset(rh,-1,sizeof rh);
memset(f,0,sizeof f);
memset(ins,0,sizeof ins);
cin>>n>>m>>K>>P;
for(int i=1;i<=m;i++)
{
int a,b,c;//起点终点距离
cin >>a>>b>>c;
add(h,a,b,c);
add(rh,b,a,c);//反图
}
dijkstra();//找到最短路
dp(1,0);//判断是否有从1开始的0环
f[1][0]=1;//设置初始值
int ans=0;
for(int i=0;i<=K;i++)
{
ans+=dp(n,i);//对每一个0,K中的值求解方案数。
ans%=P;
//cout<<ans<<"\n";
}
if(fail)cout<<-1<<"\n";//通向0的0环
else cout<<ans<<'\n';
}
signed main()
{
int T;
cin>>T;
while(T--)
{
solve();
}
}