我很不想说 在我的AC代码上我打了表,但实在没有办法了。莫名的8,9个点RE。然而即便是打表。。。也花了我很久。

这大概是NOIP2017最难的题了,为了让不懂的人更容易理解,这篇题解会比较详细

我的做法是DP,在程序中写的是记忆化搜索,下面我着重讲一下状态转移方程和程序中的一些小细节

SPFA

首先对于每组数据,SPFA直接算出dist[i],表示从节点i到节点n的最短距离。是的没有看错,是到节点n的最短距离,至于为什么呢?我在下面会很详细地讲解。但我们先得完成这个SPFA,很容易,对于题目中给的每一条边反着建,得到另外一张图,姑且叫反向图。(当然原图也还是要建的,叫正向图)

状态设计

设计状态f[u][know]表示在反向图中从节点u到节点n,与从节点u到节点n的最短路径相差等于know的路径数,即可表示为f[u][know]是满足dis(u,n)==MinDis(u,n)+know 的方案数

那么答案就是∑f[1][know] (0<=know<=k),我们会在程序之中枚举know把每个都算出来

状态转移方程

int sum=;
for (int i=head[u];i!=-;i=edge[i].next)
{
int tmp=know-(dist[edge[i].to]+edge[i].val-dist[u]);
if (tmp<||tmp>k) continue;
sum=(sum+dfs(edge[i].to,tmp))%p;
if (flag) return ;
}

直接说不好讲,现在对代码进行直接讲解

首先明确,最后sum的值就是f[u][know]的值。tmp表示得是转移到v的新的know的值(know') 也就是说,我们将状态f[u][know]由f[v][know']转移得到

也就是简单的相加统计方案数了

那么现在进入最重要的环节,这个状态转移方程式怎么推出来的。请看

<v,n>(那条绿的)减去黑边(dist[v])就是know' <u,n>(一条绿的加上黑的)减去红边(dist[u])就是know 这样的话就麻烦读者自己手推一下立马就得到状态转移方程了

方程的初始值就是f[know][0]=1

好的现在解释为什么要建反向图。

因为不是每个节点都能够到达节点n的,毕竟这是有向图,反向建图的好处就是可以完全回避进入死胡同的情况,同时也是为了spfa算出到节点n的值,这是一种常用的技巧。

下面附上代码

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#define re register int
#define fo(i,a,b) for (re i=a;i<=b;i++)
using namespace std; const int inf=0x3f;
const int maxn=+;
const int mxn=+;
int T,n,m,k,p,sum1,sum2;
int head[mxn],rev[mxn],dist[mxn],vis[mxn],wd[mxn][+],f[mxn][+];//设f[u][know]为到u点时与从终点开始的最短路的偏移量为know的时候的总方案数目。
bool flag;
struct EDGE
{
int to,next,val;
}edge[maxn],reve[maxn];
inline int read() {
char ch = getchar();int ret=;
while(ch<''||ch >'') ch=getchar();
while(ch<=''&&ch>='') ret=ret*+ch-'',ch=getchar();
return ret;
}
void init()
{
memset(head,-,sizeof(head));
memset(rev,-,sizeof(rev));
sum1=;sum2=;
memset(edge,,sizeof(edge));
memset(reve,,sizeof(reve));
memset(f,-,sizeof(f));
flag=false;
}
void add(int x,int y,int z)
{
edge[++sum1]=(EDGE){y,head[x],z};
head[x]=sum1;
}
void addr(int x,int y,int z)
{
reve[++sum2]=(EDGE){y,rev[x],z};
rev[x]=sum2;
}
void spfa()
{
queue <int> q;
memset(dist,inf,sizeof(dist));
memset(vis,,sizeof(vis));
dist[n]=;
vis[n]=;
q.push(n);
while (!q.empty())
{
int x=q.front();q.pop();vis[x]=;
for (int i=rev[x];i!=-;i=reve[i].next)
if (dist[reve[i].to]>dist[x]+reve[i].val){
dist[reve[i].to]=dist[x]+reve[i].val;
if (!vis[reve[i].to])
{
vis[reve[i].to]=;
q.push(reve[i].to);
}
}
}
}
int dfs(int u,int know)
{
if (wd[u][know]) {flag=true;return ;}
if (f[u][know]>) return f[u][know];
wd[u][know]=;
int sum=;
for (int i=head[u];i!=-;i=edge[i].next)
{
int tmp=know-(dist[edge[i].to]+edge[i].val-dist[u]);
if (tmp<||tmp>k) continue;
sum=(sum+dfs(edge[i].to,tmp))%p;
if (flag) return ;
}
if (u==n&&know==) sum=;
wd[u][know]=;
return f[u][know]=sum;
}
int main()
{
T=read();
while (T--)
{
init();
scanf("%d%d%d%d",&n,&m,&k,&p);
fo(i,,m)
{
int x=read(),y=read(),z=read();
add(x,y,z);
addr(y,x,z);
}
spfa();
int ans=;
fo(i,,k)
{
memset(wd,,sizeof(wd));
ans=(ans+dfs(,i))%p;
}
if (flag) puts("-1");else printf("%d\n",ans);
}
return ;
}

需要注意的有几个点(我自己调试中出现的问题) 1.一定加上快读。。莫名快读比scanf快了5s,怀疑洛谷评测机出了问题 2.memset不要用for循环代替(我看到讨论里有人说用for循环,我用了之后从两个RE变成了三个TLE) 3.记得每次都要初始化

那么就是这样了(省选前交篇题解膜一下)

[NOIP2017] 逛公园 解题报告(DP)的更多相关文章

  1. NOIP2017 逛公园 题解报告 【最短路 + 拓扑序 + dp】

    题目描述 策策同学特别喜欢逛公园.公园可以看成一张NNN个点MMM条边构成的有向图,且没有 自环和重边.其中1号点是公园的入口,NNN号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花 ...

  2. 洛谷 P1053 逛公园 解题报告

    P3953 逛公园 问题描述 策策同学特别喜欢逛公园. 公园可以看成一张\(N\)个点\(M\)条边构成的有向图,且没有自环和重边.其中1号点是公园的入口,\(N\)号点是公园的出口,每条边有一个非负 ...

  3. 【题解】NOIP2017逛公园(DP)

    [题解]NOIP2017逛公园(DP) 第一次交挂了27分...我是不是必将惨败了... 考虑这样一种做法,设\(d_i\)表示从该节点到n​节点的最短路径,\(dp(i,k)\)表示从\(i\)节点 ...

  4. [NOIP2017] 逛公园

    [NOIP2017] 逛公园 题目大意: 给定一张图,询问长度 不超过1到n的最短路长度加k 的1到n的路径 有多少条. 数据范围: 点数\(n \le 10^5\) ,边数\(m \le 2*10^ ...

  5. 【比赛】NOIP2017 逛公园

    考试的时候灵光一闪,瞬间推出DP方程,但是不知道怎么判-1,然后?然后就炸了. 后来发现,我只要把拓扑和DP分开,中间加一个判断,就AC了,可惜. 看这道题,我们首先来想有哪些情况是-1:只要有零环在 ...

  6. NOIP2017逛公园(dp+最短路)

    策策同学特别喜欢逛公园.公园可以看成一张N个点M条边构成的有向图,且没有 自环和重边.其中1号点是公园的入口,N号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间. 策策每天都会 ...

  7. NOIP2017普及组解题报告

    刚参加完NOIP2017普及,只考了210,于是心生不爽,写下了这篇解题报告...(逃 第一次写博,望dalao们多多指导啊(膜 第一题score,学完helloworld的人也应该都会吧,之前好多人 ...

  8. [NOIP2017]逛公园 题解

    我连D1T3都不会我联赛完蛋了 题目描述 策策同学特别喜欢逛公园.公园可以看成一张 N 个点 M 条边构成的有向图,且没有 自环和重边.其中1号点是公园的入口, N 号点是公园的出口,每条边有一个非负 ...

  9. NOIP2017逛公园(park)解题报告

    park作为今年noipday1最后一道题还是相比前面几道题还是有点难度的 首先你可以思考一下,第一天dp不见了,再看一下这题,有向图,看起来就比较像一个dp,考虑dp方程,首先肯定有一维是到哪个节点 ...

随机推荐

  1. Docker Compose + Spring Boot + Nginx + Mysql

    Docker Compose + Spring Boot + Nginx + Mysql 实践 我知道大家这段时间看了我写关于 docker 相关的几篇文章,不疼不痒的,仍然没有感受 docker 的 ...

  2. bfs初学

    BFS: ** 当知道初始和目标状态的,用双向BFS: 无权图最好用BFS 不用重复如队** 实现框架: 抄来的(来源:https://www.luogu.org/blog/stephen2333/s ...

  3. ffmpeg x264编译与使用介绍

    问题1:我用的是最新版本的ffmpeg和x264,刚刚编译出来,编译没有问题,但是在linux 环境使用ffmpeg的库时发现报错error C3861: 'UINT64_C': identifier ...

  4. 20个非常有用的Java程序片段--转

    原文地址:http://geek.csdn.net/news/detail/236591 下面是20个非常有用的Java程序片段,希望能对你有用. 1. 字符串有整型的相互转换 String a = ...

  5. js获取浏览器中相关容器的高度

    网页可见区域宽: document.body.clientWidth 网页可见区域高: document.body.clientHeight 网页可见区域宽: document.body.offset ...

  6. art-template模板渲染及其过滤器

    原生语法 使用原生语法,需要导入template-native.js文件.在HTML中定义模板,注意模板的位置,不要放到被渲染区域,防止模板丢失. <script id="tpl&qu ...

  7. Boost Asio(一)初探

    一.简介 Boost Asio ( asynchronous input and output)关注数据的异步输入输出.Boost Asio 库提供了平台无关性的异步数据处理能力(当然它也支持同步数据 ...

  8. 用IIS怎样在局域网内建网站

    IIS服务器组建一览 IIS(Internet Information Server,互联网信息服务)是一种Web(网页)服务组件,其中包括Web服务器.FTP服务器.NNTP服务器和SMTP服务器, ...

  9. 学Arduino 需要做哪些准备?(引自"知乎用户:郑兴芳,DhP"的回答)

    本人非电子专业,使用Arduino完全出于兴趣,目前主要用于实验过程中的自动化操作. 一.基础准备主要是看一些入门介绍的电子文档,如Arduino_Basic.PDF.ArduinoL2.PDF .& ...

  10. Mac 如何寻找Mac自带的IDLE

    Mac 如何寻找Mac自带的IDLE 每次要打开IDLE时,需要如下动作:打开terminal --> 输入idle --> 回车,就自动打开IDLE了 图标如下: 选择在“Finder中 ...