题解 【BZOJ4700】适者
题面
解析
看了好多dalao们的题解,然而还是不明白...
于是在想了半天后,决定自己写一篇题解。
step 1
首先,分析题意,
应该还是比较容易想到,
要一直攻击一个兵器,
直到破坏它为止。
因为迟早要把兵器都破坏完,但如果不先破坏一个的话,他就会一直攻击。
那么,就会有一个破坏兵器的先后顺序。
并且,注意到血量其实只是为了转化成攻击的次数,
因此可以用\(d_i\)表示\(i\)被攻击的次数,
另外,约定\(a_i\)为\(i\)的攻击力,
那么题目就分析完毕了!似乎说了一通废话
step 2
先考虑没有秒杀的情况,
那么,伤害最小,其实也就是\(\sum_{i=1}^{n}\) \(a_i\) \(*\) (\(\sum_{j=1}^{i}\)-\(1\)) 最小,(注意,由于最后一次攻击时兵器已经被破坏了(题目描述有问题),因此要减\(1\))
而对于两个相邻兵器\(i\),\(j\),
如果\(i\)排在\(j\)前面会更优,
并设\(X\) \(=\) \(\sum_{k=1}^{i}d_k\),
则有不等式
\(a_i\) \(*\) (\(X\)-\(1\))+$a_j $ \(*\) (\(X\)\(+\)\(d_i\) - \(1\))\(<\)\(a_j\) \(*\) (\(X\)-\(1\))+$a_i $ \(*\) (\(X\)\(+\)\(d_j\) - \(1\))
那么,可以解得\(d_i\)\(*\)\(a_j\)\(<\)\(d_j\)\(*\)\(a_i\),(自己解一下吧)
因此,将兵器按此规律排序,就是没有秒杀的最优解了似乎又是废话
但要计算出这个没有秒杀的最优解(后面要用)。
step 3
继续,在上一步的情况下考虑秒杀掉一个兵器,
设\(v_i\)为删除\(i\)后能减小的伤害,
那么对于排在\(i\)前面的兵器们,
在攻击他们的时候,\(i\)都会对基地造成伤害,
那么对于这一部分,减少的伤害就为(\(\sum_{j=1}^{i}\)\(d_j\)-\(1\))\(*\)\(a_i\)。
而对于排在\(i\)后面的所有兵器,
在攻击\(i\)时,
他们也在对基地造成伤害。
所以对于这一部分,减少的伤害为(\(\sum_{j=i+1}^{n}\)\(a_j\))\(*\)\(d_i\)。
综上,就可以得出\(v_i\)\(=\)(\(\sum_{j=1}^{i}\)\(d_j\)-\(1\))\(*\)\(a_i\)\(+\)(\(\sum_{j=i+1}^{n}\)\(a_j\))\(*\)\(d_i\)。
那么继续考虑秒杀两个兵器\(i\),\(j\),且\(i\)在\(j\)后面,
减少的伤害就是\(v_i\)\(+\)\(v_j\)\(-\)\(a_i\)\(*\)\(d_j\)(因为这一部分重复加了一次,所以减掉)。
然后,只要求出使\(v_i\)\(+\)\(v_j\)\(-\)\(a_i\)\(*\)\(d_j\)最大的\(i\),\(j\)就行了。
step 4
那么怎么求呢?
对于每个\(i\),
我们计算当它与顺序中前面的某个兵器一起被秒掉时,
能减少的伤害的最大值,
再算出\(1\)~\(n\)中这个式子最大的值就行了。
那么,我们要算出前面的兵器对它的影响,和它对后面的兵器的影响。
所以\(CDQ\)分治就成了解决问题的主要算法。
但还是无法解决问题啊!!!
到底要怎么算啊?
别着急,看我狂推公式【滑稽】。
首先我们设对于兵器\(i\),我们已经找到了一个\(j\)和它一起秒杀(重复一遍,\(j\)在\(i\)前面),
但是,却存在一个\(k\)能使答案更优,即:
\(v_i\)\(+\)\(v_k\)\(-\)\(a_i\)\(*\)\(d_k\)\(>=\)\(v_i\)\(+\)\(v_j\)\(-\)\(a_i\)\(*\)\(d_j\)
那么,约掉\(v_i\),再移下项,就变成了:
(\(d_k\)-\(d_j\))\(*\)\(a_i\)\(<=\)\(v_k\)-\(v_j\),
然后分类讨论:
- \(d_k\)\(>\)\(d_j\)时,\(a_i\)\(<=\)\(\frac{v_k-v_j}{d_k-d_j}\),
- \(d_k\)\(=\)\(d_j\)时,\(v_k\)\(>=\)\(v_j\),
- \(d_k\)\(<\)\(d_j\)时,\(a_i\)\(>=\)\(\frac{v_j-v_k}{d_j-d_k}\),
因此,我们只需要找到一个\(j\),使得找不到\(k\)满足上面的式子,
那么这个\(j\)就是最优解了。
即:
设\(y\)是\(i\)的最优解 ,
那么对于任意满足\(d_x\)\(<\)\(d_y\)\(<\)\(d_z\)(等于时要特判)的\(x\),\(z\),
都有\(\frac{v_y-v_x}{d_y-d_x}\)\(>\)\(a_i\)\(>\)\(\frac{v_y-v_z}{d_y-d_z}\),
因此,对于\(l\)~\(mid\),我们先将\(d\)按升序排列,
再用单调队列维护\(\frac{v_y-v_x}{d_y-d_x}\)的降序排列(蒟蒻的单调队列讲得似乎有点不清楚自己理解下哈)
同时,对于\(mid\)+\(1\)~\(r\)为了节省时间,将\(a\)按降序排列(所以多开几个数组),
每次在单调队列中找出第一个小于\(a_i\)的\(\frac{v_y-v_x}{d_y-d_x}\),
再用\(y\)更新\(i\)的答案就行了。
对于\(d\)和\(a\)的排序,归并时排一下就可以解决。
具体实现方式看代码吧:
#include<bits/stdc++.h>
#define db double
#define ll long long
using namespace std;
inline int read(){
ll sum=0,f=1;char ch=getchar();
while(ch>'9' || ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0' && ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return f*sum;
}
struct node{
int a/*攻击*/,d/*被打次数*/;
ll v/*被秒掉后下降的伤害*/;
}c[500001]/*敌方*/,q1[500001]/*按d升序排列*/,q2[500001]/*按a降序排列*/;
node qq[500001];//排序时临时存一下
int n,atk;
ll ans=0,ansn=0;
ll sa[500001]/*被打次数的前缀和*/,sb[500001]/*攻击的后缀和*/;
int q[500001];
bool cmp(node x,node y){
return (ll)x.d*y.a<(ll)y.d*x.a;
}
db cmp1(int x,int y){
if(q1[x].d==q1[y].d){
if(q1[y].v<=q1[x].v) return 0;
return 1e18;
}
return (db)((q1[y].v-q1[x].v)/(q1[y].d-q1[x].d));
}
void CDQ(int l,int r){
if(l==r) return ;
int mid=(l+r)>>1;
CDQ(l,mid);CDQ(mid+1,r);
int le=1,ri=0;
for(int i=l;i<=mid;i++){
while(le<ri&&cmp1(q[ri-1],q[ri])<cmp1(q[ri],i)) ri--;
q[++ri]=i;
}
for(int i=mid+1;i<=r;i++){
while(le<ri&&cmp1(q[le],q[le+1])>=q2[i].a) le++;
ans=max(ans,(ll)(q2[i].v+q1[q[le]].v-q2[i].a*q1[q[le]].d));
}
//--------------下面是排序
int pi=l,qi=mid+1,tot=l;
while(pi<=mid&&qi<=r){
if(q1[pi].d<q1[qi].d) qq[tot++]=q1[pi++];
else qq[tot++]=q1[qi++];
}
while(pi<=mid) qq[tot++]=q1[pi++];
while(qi<=r) qq[tot++]=q1[qi++];
for(int i=l;i<=r;i++) q1[i]=qq[i];
pi=l,qi=mid+1,tot=l;
while(pi<=mid&&qi<=r){
if(q2[pi].a>q2[qi].a) qq[tot++]=q2[pi++];
else qq[tot++]=q2[qi++];
}
while(pi<=mid) qq[tot++]=q2[pi++];
while(qi<=r) qq[tot++]=q2[qi++];
for(int i=l;i<=r;i++) q2[i]=qq[i];
}
int main(){
freopen("fight.in","r",stdin);
freopen("fight.out","w",stdout);
n=read();atk=read();
for(int i=1;i<=n;i++){
c[i].a=read();c[i].d=read();
c[i].d=(c[i].d-1)/atk+1;//转换为攻击的次数
}
sort(c+1,c+n+1,cmp);//无秒杀时的最优攻击顺序
for(int i=1;i<=n;i++) sa[i]=sa[i-1]+c[i].d;//d的前缀和
for(int i=n;i>=1;i--) sb[i]=sb[i+1]+c[i].a;//a的后缀和
for(int i=1;i<=n;i++) ansn+=c[i].a*(sa[i]-1);//没有秒杀时的总伤害
for(int i=1;i<=n;i++){
c[i].v=(sa[i]-1)*c[i].a+sb[i+1]*c[i].d;
}
for(int i=1;i<=n;i++) q1[i]=c[i],q2[i]=c[i];
CDQ(1,n);
printf("%lld\n",ansn-ans);
return 0;
}
题解 【BZOJ4700】适者的更多相关文章
- [BZOJ4700]适者(CDQ分治+DP/李超线段树)
如果没有秒杀,就是经典的国王游戏问题,按t/a从小到大排序即可. 考虑删除两个数i<j能给答案减少的贡献:S[i]*T[i]+P[i-1]*A[i]-A[i]+S[j]*T[j]+P[j-1]* ...
- BZOJ4700 适者(贪心+cdq分治+斜率优化)
首先考虑怎么安排攻击顺序.显然如果攻击了某台兵器就应该一直连续攻击直到将其破坏,破坏所需时间可以直接算出来,设其为b.假设确定了某个破坏顺序,如果交换相邻两个兵器,显然不会对其他兵器造成影响,两种顺序 ...
- BZOJ4700: 适者
先排序,枚举删一个点,在前面找出最优的另一个点,容易推出斜率方程,平衡树维护凸包. #include<bits/stdc++.h> using namespace std; typedef ...
- 遗传编程(GA,genetic programming)算法初探,以及用遗传编程自动生成符合题解的正则表达式的实践
1. 遗传编程简介 0x1:什么是遗传编程算法,和传统机器学习算法有什么区别 传统上,我们接触的机器学习算法,都是被设计为解决某一个某一类问题的确定性算法.对于这些机器学习算法来说,唯一的灵活性体现在 ...
- 2016 华南师大ACM校赛 SCNUCPC 非官方题解
我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ...
- noip2016十连测题解
以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...
- BZOJ-2561-最小生成树 题解(最小割)
2561: 最小生成树(题解) Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1628 Solved: 786 传送门:http://www.lyd ...
- Codeforces Round #353 (Div. 2) ABCDE 题解 python
Problems # Name A Infinite Sequence standard input/output 1 s, 256 MB x3509 B Restoring P ...
- 哈尔滨理工大学ACM全国邀请赛(网络同步赛)题解
题目链接 提交连接:http://acm-software.hrbust.edu.cn/problemset.php?page=5 1470-1482 只做出来四道比较水的题目,还需要加强中等题的训练 ...
随机推荐
- Spring中 如果该Service有多个实现类,它怎么知道该注入哪个ServiceImpl类?
方法一:Controller中注入service的时候使用@Autowired自动注入,@Qualifier("beanId") 来指定注入哪一个. 方法二:Controller中 ...
- Symfony4框架中单元测试和接口测试中的一些小坑
前提说明: symfony 版本 4.1.*,使用 composer create-project symfony/website-skeleton 进行安装. 目标:在一个单元测试用例中对当前工 ...
- spark教程(19)-sparkSQL 性能优化之谓词下推
在 sql 语言中,where 表示的是过滤,这部分语句被 sql 层解析后,在数据库内部以谓词的形式出现: 在 sparkSQL 中,如果出现 where,它会现在数据库层面进行过滤,一般数据库会有 ...
- 网络名称空间 实例研究 veth处于不同网络的路由问题
相关命令详细介绍参见 http://www.cnblogs.com/Dream-Chaser/p/7077105.html .问题: 两个网络名称空间中的两个接口veth0和veth1,如何配置net ...
- java如何防止反编译(转)
出处: java如何防止反编译 一些防止java代码被反编译的方法 综述(写在前面的废话) Java从诞生以来,其基因就是开放精神,也正因此,其可以得到广泛爱好者的支持和奉献,最终很快发展壮大,以至于 ...
- Docker——网络和存储(数据卷)
iptables -t nat -vnL |grep docker 查看docker桥接网卡:brctl show 本地端口随机映射到docker容器的80端口上: docker run -d -P ...
- Python库的优雅安装及PyCharm虚拟环境配置
一.安装python库 安装python库有几种方式: 1. 使用pip命令行,如:pip install Pillow 2. 在pycharm中安装 3. 使用Anaconda批量安装常用模块 在使 ...
- 使用kafka-eagle监控Kafka
# 监控kafka集群,开启监控趋势图使用 # 有一个问题,需要在kafka-server-start.sh文件中配置端口,有如下三种办法 # 第一种:复制并修改kafka目录,比如kafka-1,k ...
- C#面向对象17 23种设计模式
1.简单工厂模式 using System; using System.Collections.Generic; using System.Linq; using System.Text; using ...
- O063、NFS Volume Provider(Part II)
参考https://www.cnblogs.com/CloudMan6/p/5693771.html 本节开始创建 NFS volume ,操作方法和 LVM volume一样,唯一的区别是在 v ...