NOIP 2017 DAY1 T3 逛公园
题目描述 戳这里
即给一个 n 个点 m 条边的有向图。要求从 1 走到 n 。并且如果从 1 到 n 的最短路为 w 。那么所走的路线不能
大于 w + k 。问有多少条符合的方案 。并最后用 p 取模 。
分析一波
- 首先我们考虑到 k = 0 的情况 。那么就是问最短路有几条。
- 再考虑有没有 0 边 。我们需要对搜索的过程进行一定的优化 。 本题我们可以建反图,然后反向跑图,
然后预处理出哪些点时不可能到达 n 的 。
我们还需要通过 F 数组来进行状态的转移 (像dp一样转移 。 - 对于有 0 边的图来说 。 我们会发现会存在 0 环会影响到答案 。
同一个 0 环上任意一个点到 1 的最短路和到n的最短路都一样
再分析一波
- 我们可以反向跑图 + 记忆化搜索 。进行转移就行 。 再特判 0 环就行了。
注意
- 判 0 环
if(dis[v]>=dis[u]+cost[i])
- dp 注意顺序,因为有 0 边的存在,所以 0 边的两端的端点需先确定更新顺序 。
特判完后,我们只需要用 dis 的大小来作为第一关键字,用 id 来当第二关键字。
if(id[v]<id[u]+1)id[v]=id[u]+1;
- dp 如何更新。
- f [i] [j] 表示1至 i 的路径中,小于等于 dis [i] + j 的路径数。
转移也很简单:
如果 ( dis[u] + j + (u至v的长度) - dis[v] <= K )
那么就将f [v] [ dis[u] + j + (u至v的长度) - dis[v] ] += f [u] [j] ;
for(int k=0;k<=kk;k++) //dp
{
for(int j=1;j<=n;j++)
{
int u=a[j].pos,d=dis[u];
if(d>=inf)continue;
for(int i=head[u];i!=-1;i=Next[i])
{
int v=to[i];
if(cost[i]-dis[v]+d+k<=kk) //如果当前到v的状态的花费小于kk就更新
{
f[v][cost[i]-dis[v]+d+k]+=f[u][k]; //更新
f[v][cost[i]-dis[v]+d+k]%=mod;
}
}
}
}
- 不加读优就会炸
#include<bits/stdc++.h>
#define maxm 800000
#define maxn 400000
#define inf 20010100
inline int read() {
int x = 0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
using namespace std;
int cnt,from[maxm],to[maxm],cost[maxm],Next[maxm],head[maxm],cont[maxm],cb[maxn],id[maxn];
int dis[maxn],vis[maxn],f[maxn][65],ans;
int n,m,x,y,z,mod,kk;
struct kkk{
int dis,id,pos;
}a[maxn];
queue<int>q;
int cmp(kkk a,kkk b){ //排序操作
if(a.dis==b.dis)return a.id<b.id; //记得以dis为第一关键字,id为第二关键字
else return a.dis<b.dis;
}
bool SPFA(int S){ //SPFA求最短路
while(!q.empty())q.pop();
for(int i=1;i<=n;i++)dis[i]=inf,vis[i]=0,id[i]=0;
vis[S]=1;q.push(S);dis[S]=0;
int sum=0;
while(!q.empty())
{
int u=q.front();q.pop();vis[u]=0;
sum+=cb[u]+1;if(sum>2000000)return false; //卡带判0环
for(int i=head[u];i!=-1;i=Next[i])
{
int v=to[i];
if(dis[v]>=dis[u]+cost[i])
{
if(++cont[v]>=n)return false; //当然普通判0环也少不了
dis[v]=dis[u]+cost[i];
if(id[v]<id[u]+1)id[v]=id[u]+1; //id确定0边两个端点的更新顺序
if(vis[v]==0)
{
vis[v]=1;
q.push(v);
}
}
}
}
return true;
}
void add(int x,int y,int z){ //建边
cnt++;
cost[cnt]=z;cb[x]++;
from[cnt]=x;to[cnt]=y;
Next[cnt]=head[x];head[x]=cnt;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
//主函数之前的一些必要操作:
memset(head,-1,sizeof(head));
memset(cont,0,sizeof(cont));ans=0;
memset(a,0,sizeof(a));cnt=0;
n=read(),m=read(),kk=read(),mod=read();
//scanf("%d%d%d%d",&n,&m,&kk,&mod);
for(int i=1;i<=m;i++)
x=read(),y=read(),z=read(),add(x,y,z);
//scanf("%d%d%d",&x,&y,&z),add(x,y,z);
bool flag=SPFA(1);
if(flag==false)
{printf("-1\n");continue;}
//***********************以上为基本操作-分界线-以下为dp求解***********************//
for(int i=1;i<=n;i++)a[i].pos=i,a[i].dis=dis[i],a[i].id=id[i]; //dp的初始化
sort(a+1,a+n+1,cmp); //进行排序
//f[i][j]表示1至i的路径中,小于等于dis[i]+j的路径数
memset(f,0,sizeof(f));f[1][0]=1;//dp初始化
for(int k=0;k<=kk;k++) //dp
{
for(int j=1;j<=n;j++)
{
int u=a[j].pos,d=dis[u];
if(d>=inf)continue;
for(int i=head[u];i!=-1;i=Next[i])
{
int v=to[i];
if(cost[i]-dis[v]+d+k<=kk) //如果当前到v的状态的花费小于kk就更新
{
f[v][cost[i]-dis[v]+d+k]+=f[u][k];
f[v][cost[i]-dis[v]+d+k]%=mod;
}
}
}
}
for(int i=0;i<=kk;i++)
ans+=f[n][i],ans%=mod;
printf("%d\n",ans);
}
}