逛超市|图上动态规划

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 secondconst 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 3int 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();  }}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值