Codeforces 1067D - Computer Game(矩阵快速幂+斜率优化)
好题。
首先显然我们如果在某一次游戏中升级,那么在接下来的游戏中我们一定会一直打 \(b_jp_j\) 最大的游戏 \(j\),因为这样得到的期望收益最大。
因此我们设 \(dp_i\) 表示还剩 \(i\) 秒并且当前没有升级过的最大收益。
那么有 \(dp_i=\max\limits_{j}\{dp_{i-1}(1-p_j)+X(i-1)p_j+p_ja_j\}\),其中 \(X=\max\{b_jp_j\}\)。
稍微解释一下上面的转移方程,我们枚举这一次选择玩哪个游戏,设为 \(j\),那么我们有 \(p_j\) 的概率获得胜利,之后每一轮期望会获得 \(X\) 的收益,得到的总收益就是 \(X(i-1)\),此外该轮还会获得 \(a_j\) 的收益,这种情况下的期望收益就是 \(X(i-1)p_j+p_ja_j\)。如果这次游戏没有取得胜利,那么问题转化为还剩 \(i-1\) 秒的情况,最大收益为 \(dp_{i-1}\),概率为 \(1-p_j\),期望收益为 \((1-p_j)dp_{i-1}\),把两者加起来可以得到 \(dp_{i-1}(1-p_j)+X(i-1)p_j+p_ja_j\)。对所有 \(j\) 取个 \(\max\) 即可得到上面的式子。
我们设 \(S_i=X(i-1)-dp_{i-1}\),那么上面的转移方程可以写成 \(dp_i=\max\{dp_{i-1}+p_jS_i+p_ja_j\}\)。注意到 \(S_j\) 是单调不减的,因为 \(S_{j+1}-S_{j}=(iX-dp_i)-(X(i-1)-dp_{i-1})=X-(dp_i-dp_{i-1})\),而 \(dp_i-dp_{i-1}\) 这个式子我们调用它的实际意义可知,它们的差距不可能大于一轮中的最大收益 \(X\),因此 \(X-(dp_i-dp_{i-1})\ge 0\)。
注意到上面改写过的 \(dp\) 方程可以视作,我们有若干个点 \((p_j,a_jp_j)\),你要对于所有点,过其做斜率为 \(-S_i\),取最大截距作为 \(dp_i\) 相较于 \(dp_{i-1}\) 的增量,看到这样的设问我们可以很自然地想到斜率优化。具体来说我们建出这 \(n\) 个点的上凸壳,那么最优切点肯定在上凸壳上,又因为 \(S_i\) 单调递增,因此最优切点的横坐标肯定不断向右移,因此我们可以均摊 \(\mathcal O(n+T)\) 地对于每一轮 DP 求出其最优切点。
如果这题 \(T\) 的数据范围比较小那么按照上文所述进行斜率优化即可通过,不过这丧心病狂的出题人偏偏将 \(T\) 数据范围加强到 \(10^{10}\)。这就导致直接一轮轮推过去的做法无法通过,不过注意到对于上凸壳上每个点,它作为最优转移点存在的时刻是一段区间,因此我们考虑从左到右遍历上凸壳上每一个点,二分它作为最优转移点的区间的右端点 \(t\),矩阵快速幂算出 \(t\) 时刻的 DP 值,并通过判断 \(S_t\) 的最优切点是否是该点来判断应当向左还是向右二分。具体来说,对于一段连续的且最优转移点均为 \(j\) 的 DP,它们的 DP 转移均可写成以下形式:
\begin{bmatrix}
1-p_j&0&0\\
p_jX&1&0\\
p_ja_j&1&1
\end{bmatrix}=
\begin{bmatrix}dp_{i+1}&i+1&1\end{bmatrix}
\]
矩阵快速幂即可。这样复杂度 \(n\log^2n\),再加上矩阵快速幂的大常数,有亿点点危,不过如果我们对于所有 \(k\in[0,\log_2(t)]\) 预处理出矩阵的 \(2^k\) 幂,然后倍增最优转移点区间的右端点,那么时间复杂度可以做到 \(n\log n\)。
const int MAXN=1e5;
const int LOG_T=36;
int sgn(ld x){return (x<-EPS)?-1:((x<EPS)?0:1);}
struct mat{
ld a[4][4];
mat(){for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) a[i][j]=0;}
mat operator *(const mat &rhs){
mat res;
for(int i=1;i<=3;i++) for(int j=1;j<=3;j++)
for(int k=1;k<=3;k++) res.a[i][k]+=a[i][j]*rhs.a[j][k];
return res;
}
} cur,pw[LOG_T+2];
int n,a[MAXN+5],b[MAXN+5];
ll t;ld p[MAXN+5],X=0;
struct point{
ld x,y;int id;
point(ld _x=0,ld _y=0,int _id=0):x(_x),y(_y),id(_id){}
bool operator <(const point &rhs) const{
if(sgn(x-rhs.x)) return sgn(x-rhs.x)<0;
return sgn(y-rhs.y)<0;
}
} P[MAXN+5];
int stk[MAXN+5],tp=0;
void calc_hull(){
sort(P+1,P+n+1);
for(int i=1,j;i<=n;i=j+1){
j=i;while(!sgn(P[i].x-P[j].x)) j++;j--;
while(tp>1&&(P[stk[tp]].y-P[stk[tp-1]].y)*(P[j].x-P[stk[tp]].x)<
(P[stk[tp]].x-P[stk[tp-1]].x)*(P[j].y-P[stk[tp]].y)) --tp;
stk[++tp]=j;
}
}
ld calc(point p,ld x){return p.x*x+p.y;}
int main(){
scanf("%d%lld",&n,&t);
for(int i=1;i<=n;i++){
scanf("%d%d%Lf",&a[i],&b[i],&p[i]);
P[i]=point(p[i],a[i]*p[i],i);chkmax(X,b[i]*p[i]);
}
calc_hull();
ll curp=0;cur.a[1][3]=1;
// for(int i=1;i<=tp;i++) printf("(%.10Lf %.10Lf)\n",P[stk[i]].x,P[stk[i]].y);
for(int i=1;;){
ld curS=X*curp-cur.a[1][1];
// printf("%lld %.10Lf\n",curp,curS);
while(i<tp&&(P[stk[i+1]].y-P[stk[i]].y)>=-curS*(P[stk[i+1]].x-P[stk[i]].x)) i++;
int id=P[stk[i]].id;
pw[0].a[1][1]=1-p[id];pw[0].a[1][2]=0;pw[0].a[1][3]=0;
pw[0].a[2][1]=p[id]*X;pw[0].a[2][2]=1;pw[0].a[2][3]=0;
pw[0].a[3][1]=p[id]*a[id];pw[0].a[3][2]=1;pw[0].a[3][3]=1;
for(int j=1;j<=LOG_T;j++) pw[j]=pw[j-1]*pw[j-1];
for(int j=LOG_T;~j;j--) if(curp+(1ll<<j)<t){
mat nw_mat=cur*pw[j];
ld nw=X*nw_mat.a[1][2]-nw_mat.a[1][1];
if((i==tp)||calc(P[stk[i]],nw)>calc(P[stk[i+1]],nw))
curp+=(1ll<<j),cur=cur*pw[j];
} cur=cur*pw[0];curp++;if(curp==t) break;
} printf("%.10Lf\n",cur.a[1][1]);
return 0;
}
Codeforces 1067D - Computer Game(矩阵快速幂+斜率优化)的更多相关文章
- Codeforces 691E题解 DP+矩阵快速幂
题面 传送门:http://codeforces.com/problemset/problem/691/E E. Xor-sequences time limit per test3 seconds ...
- CodeForces - 691E Xor-sequences 【矩阵快速幂】
题目链接 http://codeforces.com/problemset/problem/691/E 题意 给出一个长度为n的序列,从其中选择k个数 组成长度为k的序列,因为(k 有可能 > ...
- Codeforces 691E Xor-sequences(矩阵快速幂)
You are given n integers a1, a2, ..., an. A sequence of integers x1, x2, ..., xk is called a & ...
- Codeforces 954 dijsktra 离散化矩阵快速幂DP 前缀和二分check
A B C D 给你一个联通图 给定S,T 要求你加一条边使得ST的最短距离不会减少 问你有多少种方法 因为N<=1000 所以N^2枚举边数 迪杰斯特拉两次 求出Sdis 和 Tdis 如果d ...
- POJ 3150 Cellular Automaton --矩阵快速幂及优化
题意:给一个环,环上有n块,每块有个值,每一次操作是对每个点,他的值变为原来与他距离不超过d的位置的和,问k(10^7)次操作后每块的值. 解法:一看就要化为矩阵来做,矩阵很好建立,大白书P157页有 ...
- hdu 1575 Tr A(矩阵快速幂乘法优化算法)
Problem Description A为一个方阵,则Tr A表示A的迹(就是主对角线上各项的和),现要求Tr(A^k)%. Input 数据的第一行是一个T,表示有T组数据. 每组数据的第一行有n ...
- hihocoder第41周 骨牌覆盖(矩阵快速幂)
由于棋盘只有两行,所以如果第i列的骨牌竖着放,那么就转移为第1列到第i-1列骨牌有多少种摆法 如果第一行第i列骨牌横着放,那么第二行第i列也要横着放,那么就转移为了第1列到第i-2列骨牌有多少种方法 ...
- 【BZOJ1009】GT考试(KMP算法,矩阵快速幂,动态规划)
[BZOJ1009]GT考试(KMP算法,矩阵快速幂,动态规划) 题面 BZOJ 题解 看到这个题目 化简一下题意 长度为\(n\)的,由\(0-9\)组成的字符串中 不含串\(s\)的串的数量有几个 ...
- 2019.02.11 bzoj4818: [Sdoi2017]序列计数(矩阵快速幂优化dp)
传送门 题意简述:问有多少长度为n的序列,序列中的数都是不超过m的正整数,而且这n个数的和是p的倍数,且其中至少有一个数是质数,答案对201704082017040820170408取模(n≤1e9, ...
随机推荐
- 【java】关键字this怎么用?
摘至:https://www.php.cn/java/guide/462643.html java中this关键字的用法:1.当成员变量和局部变量重名时,在方法中使用this时,表示的是该方法所在类中 ...
- .net Xml加密解密操作
生成密钥的方法: /// <summary>生成RSA加密 解密的 密钥 /// 生成的key就是 方法EncryptByRSA与DecryptByRSA用的key了 /// </s ...
- Java:HashMap类小记
Java:HashMap类小记 对 Java 中的 HashMap类,做一个微不足道的小小小小记 概述 HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致.由于要保证键的唯一.不重复 ...
- [Beta]the Agiles Scrum Meeting 3
会议时间:2020.5.14 20:00 1.每个人的工作 今天已完成的工作 成员 已完成的工作 yjy 实现前端界面美化 tq 实现查看.删除测试点功能的前端修复功能中的bug wjx 升级系统实现 ...
- CF375D Tree and Queries 题解
感觉CF的题目名都好朴素的样子 你谷链接 首先这题显然是个dsu on tree 但是我不会. 其次这题显然是个莫队.这我会啊! 然后会发现好像不是很对劲.因为每次询问都有一个k,貌似和传统的莫队数颜 ...
- STM32的I2C框图详解及通讯过程
STM32 的I2C 特性及架构 如果我们直接控制STM32 的两个GPIO 引脚,分别用作SCL 及SDA,按照上述信号的时序要求,直接像控制LED 灯那样控制引脚的输出(若是接收数据时则读取SDA ...
- Spring Cache 带你飞(一)
Spring 3.1 版本引入基于 annotation 的 cache 技术,提供了一套抽象的缓存实现方案,通过注解方式使用缓存,基于配置的方式灵活使用不同缓存组件.代码具有相当的灵活性和扩展性,本 ...
- Spring Boot 2.5.0 重新设计的spring.sql.init 配置有何用?
前几天Spring Boot 2.5.0发布了,其中提到了关于Datasource初始化机制的调整,有读者私信想了解这方面做了什么调整.那么今天就要详细说说这个重新设计的配置内容,并结合实际情况说说我 ...
- hdu 1158 Employment Planning(DP)
题意: 有一个工程需要N个月才能完成.(n<=12) 给出雇佣一个工人的费用.每个工人每个月的工资.解雇一个工人的费用. 然后给出N个月所需的最少工人人数. 问完成这个项目最少需要花多少钱. 思 ...
- 第11课 OpenGL 飘动的旗帜
飘动的旗帜: 这一课从第六课的代码开始,创建一个飘动的旗帜.我相信在这课结束的时候,你可以掌握纹理映射和混合操作. 大家好!对那些想知道我在这里作了些什么的朋友,您可以先按文章的末尾所列出的链接,下载 ...