[NOIP2017] 逛公园 解题报告(DP)
我很不想说 在我的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)的更多相关文章
- NOIP2017 逛公园 题解报告 【最短路 + 拓扑序 + dp】
题目描述 策策同学特别喜欢逛公园.公园可以看成一张NNN个点MMM条边构成的有向图,且没有 自环和重边.其中1号点是公园的入口,NNN号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花 ...
- 洛谷 P1053 逛公园 解题报告
P3953 逛公园 问题描述 策策同学特别喜欢逛公园. 公园可以看成一张\(N\)个点\(M\)条边构成的有向图,且没有自环和重边.其中1号点是公园的入口,\(N\)号点是公园的出口,每条边有一个非负 ...
- 【题解】NOIP2017逛公园(DP)
[题解]NOIP2017逛公园(DP) 第一次交挂了27分...我是不是必将惨败了... 考虑这样一种做法,设\(d_i\)表示从该节点到n节点的最短路径,\(dp(i,k)\)表示从\(i\)节点 ...
- [NOIP2017] 逛公园
[NOIP2017] 逛公园 题目大意: 给定一张图,询问长度 不超过1到n的最短路长度加k 的1到n的路径 有多少条. 数据范围: 点数\(n \le 10^5\) ,边数\(m \le 2*10^ ...
- 【比赛】NOIP2017 逛公园
考试的时候灵光一闪,瞬间推出DP方程,但是不知道怎么判-1,然后?然后就炸了. 后来发现,我只要把拓扑和DP分开,中间加一个判断,就AC了,可惜. 看这道题,我们首先来想有哪些情况是-1:只要有零环在 ...
- NOIP2017逛公园(dp+最短路)
策策同学特别喜欢逛公园.公园可以看成一张N个点M条边构成的有向图,且没有 自环和重边.其中1号点是公园的入口,N号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间. 策策每天都会 ...
- NOIP2017普及组解题报告
刚参加完NOIP2017普及,只考了210,于是心生不爽,写下了这篇解题报告...(逃 第一次写博,望dalao们多多指导啊(膜 第一题score,学完helloworld的人也应该都会吧,之前好多人 ...
- [NOIP2017]逛公园 题解
我连D1T3都不会我联赛完蛋了 题目描述 策策同学特别喜欢逛公园.公园可以看成一张 N 个点 M 条边构成的有向图,且没有 自环和重边.其中1号点是公园的入口, N 号点是公园的出口,每条边有一个非负 ...
- NOIP2017逛公园(park)解题报告
park作为今年noipday1最后一道题还是相比前面几道题还是有点难度的 首先你可以思考一下,第一天dp不见了,再看一下这题,有向图,看起来就比较像一个dp,考虑dp方程,首先肯定有一维是到哪个节点 ...
随机推荐
- tensorflow利用预训练模型进行目标检测(一):安装tensorflow detection api
一.tensorflow安装 首先系统中已经安装了两个版本的tensorflow,一个是通过keras安装的, 一个是按照官网教程https://www.tensorflow.org/install/ ...
- XCode下Swift – WebView IOS demo
简介 我今天用Mac升级了XCode到8.1,Swift版本应该到了swift3,按网上的demo写webview的例子,报一堆错,整了一天才搞定,不想其他人踩坑了! XCode8.1 ,swift3 ...
- php面向对象之__isset和__unset
php面向对象之__isset和__unset 一.简介 __isset和__unset都是对不可访问属性的操作,前者是检验的时候自动调用,后者是销毁的时候自动调用. 比如说在类外访问private的 ...
- github结合TortoiseGit使用sshkey,无需每次输入账号和密码
首先需要明确,github上支持三种方式进行项目的clone https,ssh,subversion ssh的方式 git@github.com:用户名/版本库t.git ...
- ubuntu在桌面创建快捷方式
在/usr/share/applications下列出 *.desktop文件 例如: 首先查看所要创建的快捷方式有么有: ls /usr/share/applications | grep ecli ...
- 列表查询组件代码, 简化拼接条件SQL语句的麻烦
列表查询组件代码, 简化拼接条件SQL语句的麻烦 多条件查询
- innobackupex: Error: --decompress requires qpress
数据库版本:5.6.16系统版本:cenos 6.5通过percona-xtranbackup恢复数据库报错(软件版本:percona-xtrabackup-2.1.9-744.rhel6.x8 ...
- ASP版_阿里大于短信API Demo
阿里大于申请地址:http://www.alidayu.com 阿里大于短信发送Demo: ******index.asp************* <%@LANGUAGE="VBSC ...
- ABBYY FineReader去他的光棍节,我要我的双十一
今天就是双十一,全民剁手的双十一,一年仅一次的双十一,不只是半价的双十一.....此时此刻,多少钱拿起手机在疯狂购物,又有多少人死守着电脑,不敢怠慢一丁点机会,买着买着购物车就空了,然后才发现,咦!超 ...
- java容器基础
总结一下学过的java容器知识. 一.java容器框架 由于之前学习的java容器类比较混乱,先简单的整理一下java集合框架. 首先,像这种图,网上到处都是,因为这个也算比较准确吧,我也懒得自己画了 ...