[BZOJ4899]:记忆的轮廓(概率DP)
题目传送门
题目描述
输入格式
输出格式
样例
3 7 2
1 4
2 5
3 6
3 7
数据范围与提示
题解
设f[i]表示正确节点i走到n的期望步数,显然f[n]=0,我们倒着递推。
$ f[i]=1+ \frac{1}{d[i]} ×f[i+1]+ \frac{1}{d[i]}× \sum g[j]+f[i] $[j是i的错误儿子]
引用:移项得f[i]=d[i]+f[i+1]+s[i]
引用:复杂度线性。
首先我们需要预处理一个a[i,j],表示存档点为i,从i开始走到正确节点j的期望步数(中间不能存档)。
显然有边界条件a[i,i]=0。对于i<j,可以列出递推式:
a[i,j]=a[i,j-1]+1+1/d[j-1]*∑{g[k]+a[i,j]}[k是j-1的错误儿子]
引用:移项得a[i,j]=a[i,j-1]*d[j-1]+d[j-1]+s[j-1]
引用:可以用n^2的时间预处理a,然后做dp就很好转移了。
枚举下一次的存档点k,那么f[i,j]可以由f[k,j-1]+a[i,k]转移而来。
引用:复杂度O(n2p)
我们来估计答案的上界。考虑一种可行方案,每n/p个正确节点就设立一次存档位置,那么答案最大是多少呢?考虑最坏情况,观察a的转移,应该每变 换一次存档点,大约需要3(n/p)*s[i]+3(n/p-1)*s[i+1]+3(n/p-2)*s[i+2]+……
引用:因为最多m个节点,s的上限是1500(实际上也远远达不到),把所有s都视为这个上限,提取公因数,计算一下那个等比数列求和,由于p是有下界的, 因此n/p有上界14,发现最后也就是个12位数的样子,那么我们估计出答案最大也不会超过这个,可以放心做了。而至于a会爆炸的问题,double是可以 存很多位的,而且太大的a肯定不可能被用上。
那么其实,针对答案不会特别大,a的增长又很恐怖,我们还可以思考对70%的算法优化。那就是设定一个常数step,每次转移最多从距当前step步远的 位置转移过来。step取40多基本不会有问题了,因为a的下界已经是2^40了,而答案的上界远远没有达到,经过精确计算还可以再把step调小一点。
引用:复杂度O(np log ans)
代码时刻
#include<bits/stdc++.h>
using namespace std;
struct rec
{
int nxt;
int to;
}e[5000];
int head[5000],cnt;
int n,m,p;
double g[5000],s[5000],dp[5000];
bool vis[5000];
int du[5000];
void pre_work()//多测不清空,爆零两行泪……
{
memset(head,0,sizeof(head));
memset(dp,0,sizeof(dp));
memset(g,0,sizeof(g));
memset(s,0,sizeof(s));
memset(du,0,sizeof(du));
cnt=0;
}
void add(int x,int y)
{
e[++cnt].nxt=head[x];
e[cnt].to=y;
head[x]=cnt;
}
void dfsgetG(int x)//计算g数组
{
vis[x]=1;
g[x]=1;
for(int i=head[x];i;i=e[i].nxt)
{
dfsgetG(e[i].to);
g[x]+=g[e[i].to]*1.0/(double)du[x];
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
pre_work();
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n-1;i++)du[i]=1;//1-(n-1)中,每一个节点都要加上它到下一个正确节点的边
for(int i=n+1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
du[a]++;
add(a,b);
}
for(int i=n+1;i<=m;i++)
{
if(vis[i])continue;
dfsgetG(i);
}
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=e[j].nxt)
s[i]+=g[e[j].to];//计算s数组
for(int i=n-1;i;i--)//倒推计算答案
dp[i]=dp[i+1]+du[i]+s[i];
cout<<fixed<<setprecision(4)<<dp[1]<<endl;//保留小数输出
}
return 0;
}
70%代码:
#include<bits/stdc++.h>
using namespace std;
struct rec
{
int nxt;
int to;
}e[2000];
int head[2000],cnt;
int n,m,p;
double g[2000],s[2000],dp[2000][2000],Map[2000][2000];
bool vis[2000];
int du[2000];
void pre_work()
{
for(int i=0;i<=1;i++)e[i].nxt=e[i].to=0;
memset(head,0,sizeof(head));
memset(dp,127,sizeof(dp));
memset(g,0,sizeof(g));
memset(s,0,sizeof(s));
memset(du,0,sizeof(du));
memset(Map,0,sizeof(Map));
memset(vis,0,sizeof(vis));
cnt=0;
}
void add(int x,int y)
{
e[++cnt].nxt=head[x];
e[cnt].to=y;
head[x]=cnt;
}
void dfsgetG(int x)
{
vis[x]=1;
g[x]=1;
for(int i=head[x];i;i=e[i].nxt)
{
dfsgetG(e[i].to);
g[x]+=g[e[i].to]*1.0/(double)du[x];
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
pre_work();
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n-1;i++)du[i]=1;
for(int i=n+1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
du[a]++;
add(a,b);
}
for(int i=n+1;i<=m;i++)
{
if(vis[i])continue;
dfsgetG(i);
}
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=e[j].nxt)
s[i]+=g[e[j].to];
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
Map[i][j]=(Map[i][j-1]+1)*(double)du[j-1]+s[j-1];//计算a数组
dp[n][1]=0;//dp初始
for(int j=2;j<=p;j++)
for(int i=1;i<=n;i++)
for(int k=i+1;k<=n;k++)
dp[i][j]=min(dp[i][j],dp[k][j-1]+Map[i][k]);
cout<<fixed<<setprecision(4)<<dp[1][p]<<endl;
}
return 0;
}
100%算法:
#include<bits/stdc++.h>
using namespace std;
struct rec
{
int nxt;
int to;
}e[2000];
int head[2000],cnt;
int n,m,p;
double g[2000],s[2000],dp[2000][2000],Map[2000][2000];
bool vis[2000];
int du[2000];
void pre_work()
{
for(int i=0;i<=1;i++)e[i].nxt=e[i].to=0;
memset(head,0,sizeof(head));
memset(dp,127,sizeof(dp));
memset(g,0,sizeof(g));
memset(s,0,sizeof(s));
memset(du,0,sizeof(du));
memset(Map,0,sizeof(Map));
memset(vis,0,sizeof(vis));
cnt=0;
}
void add(int x,int y)
{
e[++cnt].nxt=head[x];
e[cnt].to=y;
head[x]=cnt;
}
void dfsgetG(int x)
{
vis[x]=1;
g[x]=1;
for(int i=head[x];i;i=e[i].nxt)
{
dfsgetG(e[i].to);
g[x]+=g[e[i].to]*1.0/(double)du[x];
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
pre_work();
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n-1;i++)du[i]=1;
for(int i=n+1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
du[a]++;
add(a,b);
}
for(int i=n+1;i<=m;i++)
{
if(vis[i])continue;
dfsgetG(i);
}
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=e[j].nxt)
s[i]+=g[e[j].to];
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
Map[i][j]=(Map[i][j-1]+1)*(double)du[j-1]+s[j-1];
dp[n][1]=0;
for(int j=2;j<=p;j++)
for(int i=1;i<=n;i++)
for(int k=i+1;k<=min(n,i+12);k++)//优化上界
dp[i][j]=min(dp[i][j],dp[k][j-1]+Map[i][k]);
cout<<fixed<<setprecision(4)<<dp[1][p]<<endl;
}
return 0;
}
rp++
[BZOJ4899]:记忆的轮廓(概率DP)的更多相关文章
- [bzoj4899]记忆的轮廓 题解(毒瘤概率dp)
题目背景 四次死亡轮回后,昴终于到达了贤者之塔,当代贤者夏乌拉一见到昴就上前抱住了昴“师傅!你终于回来了!你有着和师傅一样的魔女的余香,肯定是师傅”.众所周知,大贤者是嫉妒魔女沙提拉的老公,400年前 ...
- Bzoj4899 记忆的轮廓
B. 记忆的轮廓 题目描述 通往贤者之塔的路上,有许多的危机.我们可以把这个地形看做是一颗树,根节点编号为1,目标节点编号为n,其中1-n的简单路径上,编号依次递增,在[1,n]中,一共有n个节点.我 ...
- BZOJ4899: 记忆的轮廓【概率期望DP】【决策单调性优化DP】
Description 通往贤者之塔的路上,有许多的危机. 我们可以把这个地形看做是一颗树,根节点编号为1,目标节点编号为n,其中1-n的简单路径上,编号依次递增, 在[1,n]中,一共有n个节点.我 ...
- BZOJ4899 记忆的轮廓(概率期望+动态规划+决策单调性)
容易发现跟树没什么关系,可以预处理出每个点若走向分叉点期望走多少步才能回到上个存档点,就变为链上问题了.考虑dp,显然有f[i][j]表示在i~n中设置了j个存档点,其中i设置存档点的最优期望步数.转 ...
- BZOJ4832: [Lydsy1704月赛]抵制克苏恩 (记忆化搜索 + 概率DP)
题意:模拟克苏恩打奴隶战对对方英雄所造成的伤害 题解:因为昨(今)天才写过记忆化搜索 所以这个就是送经验了 1A还冲了个榜 但是我惊奇的发现我数组明明就比数据范围开小了啊??? #include &l ...
- zoj 3640 Help Me Escape 概率DP
记忆化搜索+概率DP 代码如下: #include<iostream> #include<stdio.h> #include<algorithm> #include ...
- bzoj 4899 记忆的轮廓 题解(概率dp+决策单调性优化)
题目背景 四次死亡轮回后,昴终于到达了贤者之塔,当代贤者夏乌拉一见到昴就上前抱住了昴“师傅!你终于回来了!你有着和师傅一样的魔女的余香,肯定是师傅”.众所周知,大贤者是嫉妒魔女沙提拉的老公,400年前 ...
- 记忆的轮廓 期望 四边形不等式dp|题解
记忆的轮廓 题目描述 通往贤者之塔的路上,有许多的危机.我们可以把这个地形看做是一颗树,根节点编号为1,目标节点编号为n,其中1-n的简单路径上,编号依次递增,在[1,n]中,一共有n个节点.我们把编 ...
- HDU 5001 概率DP || 记忆化搜索
2014 ACM/ICPC Asia Regional Anshan Online 给N个点,M条边组成的图,每一步能够从一个点走到相邻任一点,概率同样,问D步后没走到过每一个点的概率 概率DP 測 ...
随机推荐
- art-template在项目中的应用
art-template 是一个简约.超快的模板引擎.它采用作用域预声明的技术来优化模板渲染速度,从而获得接近 JavaScript 极限的运行性能,并且同时支持 NodeJS 和浏览器. 下面介绍在 ...
- HDU4791【杂】
题意: 给你一个从0开始的区间si,每个区间是前闭后开,[ s[i] , s[i+1] ), 然后再给你个一个pi,代表你在区间[ s[i] , s[i+1] )里面买东西的单价是pi,给出的s1一定 ...
- Codeforces Round #377 (Div. 2)A,B,C,D【二分】
PS:这一场真的是上分场,只要手速快就行.然而在自己做的时候不用翻译软件,看题非常吃力非常慢,还有给队友讲D题如何判断的时候又犯了一个毛病,一定要心平气和,比赛也要保证,不要用翻译软件做题: Code ...
- ThinkPHP3.2.3学习笔记6---专题---数据分页
http://document.thinkphp.cn/manual_3_2.html#data_page thinkphp3.2.3中分类的功能调用的文件$THINKPHP_HOME/ThinkPH ...
- 进击python第三篇:基础
基础拾遗 序列解包 例: >>>x,y,z=1,2,3 >>>print x,y,z 1 2 3 交换变量也是没问题 >>>x,y=y,x > ...
- 织梦cms 应急响应 修复建议
通过分析log日志,可以知道攻击者的IP 攻击时间 和具体操作 本片文章为内网测试,通过分析日志,进行复现攻击流程,同时对网站的后门给予修复建议 通过分析日志可以知道,攻击者使用了扫描工具进行网站扫描 ...
- swipe轮播插件零基础实用
此篇博客整理了常用的轮播效果,适用于所有开发人员 swipe是当下相对而言较好用的轮播插件,下面是博主整理的demo源代码,可直接上手(备注:需自己手动swipe所需的j和css) 此段代码总共是有三 ...
- Linux (二)
PS :显示系统进程 -a :显示所有进程(包括其他用户的进程) -u :用户以及其他详细信息 -x :显示没有控制终端的进程 -ef :显示所有 top :用于动态地监视进程活动与系统负载的信息 p ...
- Curious Array Codeforces - 407C(高阶差分(?)) || sequence
https://codeforces.com/problemset/problem/407/C (自用,勿看) 手模一下找一找规律,可以发现,对于一个修改(l,r,k),相当于在[l,r]内各位分别加 ...
- CD4051的切换时间
CD4051:1 2 4 5 12 13 14 15 8个选择IO输入/输出端:3:I/O6:片选低电平有效,搞定平所有通道不通9 10 11:地址选择:功能:通过地址选择译码8个中的某个通道与3脚 ...