题目描述

三体人将对地球发起攻击。为了抵御攻击,地球人派出了 $A × B × C$ 艘战舰,在太 空中排成一个 $A$ 层 $B$ 行 $C$ 列的立方体。其中,第 $i$ 层第 $j$ 行第 $k$ 列的战舰(记为战舰 $d(i, j,k)$)的生命值为 $d_{i, j,k}$。

三体人将会对地球发起 m 轮“立方体攻击”,每次攻击会对一个小立方体中的所有 战舰都造成相同的伤害。具体地,第 t 轮攻击用 7 个参数 $la_t ,ra_t , lb_t ,rb_t , lc_t ,rc_t , h_t$ 描述; 所有满足 $i ∈ [la_t ,ra_t], j ∈ [lb_t,rb_t], k ∈ [lc_t,rc_t]$ 的战舰 $(i, j, k)$ 会受到 $h_t$ 的伤害。如果一个 战舰累计受到的总伤害超过其生命值,那么这个战舰会爆炸。

地球指挥官希望你能告诉他,第一艘爆炸的战舰是在哪一轮攻击后爆炸的。

输入格式

从文件 attack.in 中读入数据。

第一行包括 4 个正整数 $A,B,C,m$;

第二行包含 $A × B × C$ 个整数,其中第 $((i − 1) × B + (j − 1)) × C + (k − 1) + 1$ 个数 为 $d_{i,j,k}$;

第 3 到第 $m + 2$ 行中,第 $t − 2$ 行包含 $7$ 个正整数 $la_t ,ra_t , lb_t ,rb_t , lc_t ,rc_t , h_t$。

输出格式

输出到文件 attack.out 中。

输出第一个爆炸的战舰是在哪一轮攻击后爆炸的。保证一定存在这样的战舰。

样例 1 输入

2 2 2 3
1 1 1 1 1 1 1 1
1 2 1 2 1 1 1
1 1 1 2 1 2 1
1 1 1 1 1 1 2

样例 1 输出

2

样例 1 解释

在第 $2$ 轮攻击后,战舰 $(1, 1, 1)$ 总共受到了 $2$ 点伤害,超出其防御力导致爆炸。

子任务

对于 $10\%$ 的数据,$B = C = 1$;

对于 $20\%$ 的数据,$C = 1$;

对于 $40\%$ 的数据,$A × B × C, m ≤ 10, 000$;

对于 $70\%$ 的数据,$A,B,C ≤ 200$;

对于所有数据,$A × B × C ≤ 10^6 , m ≤ 10^6 , 0 ≤ d_{i, j,k},h_t ≤ 10^9$。

题解

讲道理,这应该是网上第一篇详细讲这题的题解了。

之前翻这题题解的时候,大部分人都“只打了过70分的暴力”(实际上是50分复杂度的),好像还有人说若有时间复杂度更低的做法,请指教……

那这就是他需要的做法了吧。

50pts

随便$O(ABC*m)$的暴力就行。

100pts

首先说一下,满分做法不需要数据结构,更不需要什么三维线段树,应该没超过省选难度。

但是比较难想,亲自做一下就能体验到。

这种题显然是有单调性的,即飞船不会加血,如果它在某一秒被打到0血以下,那它以后就一直在0血以下了。

所以可以二分答案(攻击次数),然后判断此时是否存在血量$\lt 0$ 的飞船,如果有就降低答案的二分范围,没有就升高答案的二分范围。

虽然存在单调性,但二分判断好像还得暴力进行每次攻击操作,于是二分就没用了。

简单地说,要让二分上场,我们就得用$O(m)$的时间求出$m$次操作后所有飞船剩余的血量。

三维区间减法的不好想,可以先降为一维。

一维区间减法除了用段树之外,还有什么方法维护?

差分!

差分具体是个什么东西?

给你一个序列$a$,设一个序列$s$为$a$序列中相邻两个数的差,即$s(i)=a(i)-a(i-1)$。特别的,未赋值的$a(0)$为$0$。

我们发现,这样一个数就可以用前面所有的差的和来表示,即$a(i)\space =\space a(i)-a(i-1)+a(i-1)-a(i-2)+...+a(1)-a(0)\space =\space s(i)+s(i-1)+...+s(1)$

这就是差的前缀和。

如果我们只想快速得到少量位置的当前数的话,应该用树状数组维护差分。

但我们现在需要知道所有的数(判断是否存在小于$0$的数),就没必要再用一个$log$维护了,直接线性递推$s(i)$求出每个$a(i)$。

这样有什么好处呢?

当进行区间减法(区间加法同理)时,区间内的数彼此的相对差是不变的(都减少了同一个值,差没变),只有两端的差变了。所以每次区间减法只需要修改两端的差分值。

比如有个数列$a$:1 2 3 2 3 1

对应的差分值$s$:1 1 1 -1 1 -2

现在让$[3,5]$区间都$-1$。

只有$2,3$和$5,6$的差变了,$a(3)-a(2)$减少了$1$,$a(6)-a(5)$增加了$1$。

于是直接修改第$3$和第$6$个位置的差分值。

难度升级,考虑二维。

二维的差分变化情况就是这样

比如一次操作要把图中四个点形成的矩形区间的所有值$-1$,那每个角点的差分值变化就是点旁标的数字。

再推广到三维,就是这样(我还亲自连了线……)

比如一次操作要对图中八个点形成的立体区间的所有值$-1$,那每个角点的差分值变化就是点旁标的数字。

其实,我们会发现一个规律,就是相邻的两个点的差分值一定是相反的。

原因很简单:由于是区间运算,不管在哪一维度,只要前面有一个点的差分值增大或减小,后面就一定有一个点把差分值补回去,这样才能保证不修改到区间后的数。

所以可以用类似二分图匹配的方法计算多维差分时,操作空间的每个顶点的差分值的正负。这里只有三维,所以可以直接赋。

三维差分计算每个位置的当前数的方法:递推求以 $(0,0,0)$ 为左下角,当前点为右上角的立方体中所有位置的差分和。

这样的时间复杂度是$O(维度*空间大小)$的,对于本题就是$O(3*ABC)$。

以上是用$O(m)$的时间求出$m$次操作后所有飞船剩余的血量的方法。这种方法只能快速求出一定次操作后的差分值,对于不同的操作次数,得到每个位置的数所需的时间复杂度很高(得暴力跑一遍$A*B*C$的矩阵),所以不能过多次得到整个矩阵当前每个位置的数(暴力是能的)。因此套用二分就得到了与暴力不同的高效做法。

总结一下,就是对于前$m$个操作,每个操作都修改8个位置的差分值,都操作完后多维差分计算每个位置的数,最后暴力判断每个数是否$\lt 0$ 即可。(只要找到一个就可以退出$check$)

外面套二分,时间复杂度$O(log(m)*(m+ABC))$。

时间复杂度如何优化到理论$O(m+ABC)$

首先,我们的二分有特殊性质,即相邻两次二分的答案的变化量依次为 $\frac{m}{2},\frac{m}{4},\frac{m}{8}$,以此类推。

图中的蓝色编号为二分答案顺序,下面的彩色细线为移动长度。

由于这题比较特殊,二分的是攻击次数,所以我们不必每次二分都重新差分前面的攻击操作,造成大量重复计算,而直接在原差分值的基础上修改二分答案的变化量次即可。

根据调和级数,这样的总差分次数是 $\frac{m}{2}+\frac{m}{4}+\frac{m}{8}+...\le 1$,所有差分修改的总时间复杂度就变成了$O(m)$。

如果你不理解调和级数,我这里有个通俗易懂的解释能证明调和级数:把上式结果转为二进制,由于每个加数都把互不相同的一位都加了$1$,表示出来就是$0.1111...$(小数位可以有任意多个$1$),可以看出它无限接近但不等于$1$。

这个优化是可以实际操作的,于是时间复杂度变成了$O(m+log(m)*ABC)$。

那$ABC$呢?

可以再考虑一下这题的特征:任意飞船血量是单调不上升的。

这就说明,对于二分出的一个攻击次数$m$,如果存在血量$\lt 0$ 的飞船,我们肯定要降低答案的二分范围,而此时那些血量$\ge 0$ 的飞船在减少被攻击次数后血量肯定还$\ge 0$,依然不可能成为答案,所以我们可以用链表存储飞船矩阵,然后在上述情况时删掉这些位置。

但实际上三维链表很难写,所以最多记个$vis$数组记录被删掉的位置。

而且这个优化仅限于理论,实际上它的优化效果是不定的,时间复杂度最优是$O(m+ABC)$,最坏还是$O(m+log(m)*ABC)$(正好要进行完$m$次攻击后才能出现一个挂掉的飞船时,上述优化就没法用)。

代码是$syf$大佬的 $AC\space code$,格式嘛……

 #include<bits/stdc++.h>
#define rep(i,x,y) for(register int i=(x);i<=(y);i++)
#define dwn(i,x,y) for(register int i=(x);i>=(y);i--)
#define maxn 1000010
#define LL long long
using namespace std;
int read()
{
int x=,f=;char ch=getchar();
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')f=-,ch=getchar();
while(isdigit(ch))x=(x<<)+(x<<)+ch-'',ch=getchar();
return x*f;
}
void write(int x)
{
int f=;char ch[];
if(!x){putchar(''),putchar('\n');return;}
if(x<)x=-x,putchar('-');
while(x)ch[++f]=x%+'',x/=;
while(f)putchar(ch[f--]);
putchar('\n');
}
LL qh[maxn],a[maxn],s[maxn],d[maxn];
int qxa[maxn],qya[maxn],qza[maxn],qxb[maxn],qyb[maxn],qzb[maxn],A,B,C,m,ans;
int getx(int x,int y,int z){return (x*B+y)*C+z+;}
void getp(int x,int px,int py,int pz){x--;pz=x%C,x/=C,py=x%B,pz=x/B;return;}
void add(int x,int y,int z,LL h){if(x<=(A-)&&y<=(B-)&&z<=(C-))a[getx(x,y,z)]+=h;}
void atk(int xa,int ya,int za,int xb,int yb,int zb,LL h)
{
add(xa,ya,za,h),add(xa,ya,zb+,-h),add(xa,yb+,za,-h),add(xa,yb+,zb+,h);
add(xb+,ya,za,-h),add(xb+,ya,zb+,h),add(xb+,yb+,za,h),add(xb+,yb+,zb+,-h);
}
int check()
{
rep(i,,A-)rep(j,,B-)rep(k,,C-)s[getx(i,j,k)]=a[getx(i,j,k)];
if(C!=)rep(i,,A-)rep(j,,B-)rep(k,,C-)s[getx(i,j,k)]+=s[getx(i,j,k-)];//,cout<<i<<" "<<j<<" "<<k<<"+1 "<<s[getx(i,j,k-1)]<<endl;
if(B!=)rep(i,,A-)rep(j,,B-)rep(k,,C-)s[getx(i,j,k)]+=s[getx(i,j-,k)];//,cout<<i<<" "<<j<<" "<<k<<"+2 "<<s[getx(i,j-1,k)]<<endl;
if(A!=)rep(i,,A-)rep(j,,B-)rep(k,,C-)s[getx(i,j,k)]+=s[getx(i-,j,k)];//,cout<<<<"+3 "<<s[getx(i-1,j,k)]<<endl;
rep(i,,A-)rep(j,,B-)rep(k,,C-)if(s[getx(i,j,k)]>d[getx(i,j,k)])return ;
return ;
}
void getans(int L,int R,int lastmid)
{
if(L>R)return;
int mid=(L+R)>>;
if(mid>lastmid)rep(i,lastmid+,mid)/*cout<<"mid:"<<mid<<" add:"<<i<<endl,*/atk(qxa[i],qya[i],qza[i],qxb[i],qyb[i],qzb[i],qh[i]);
if(mid<lastmid)rep(i,mid+,lastmid)/*cout<<"mid:"<<mid<<" del:"<<i<<endl,*/atk(qxa[i],qya[i],qza[i],qxb[i],qyb[i],qzb[i],-qh[i]);
int f=check();
//cout<<"mid:"<<mid;
//cout<<"now:";
//rep(i,0,A-1)rep(j,0,B-1)rep(k,0,C-1)cout<<s[getx(i,j,k)]<<" ";cout<<endl;
if(f){ans=min(ans,mid);getans(L,mid-,mid);}
else getans(mid+,R,mid);
}
int main()
{
//freopen("attack.in","r",stdin);
//freopen("attack.out","w",stdout);
A=read(),B=read(),C=read(),ans=m=read();
rep(i,,A-)rep(j,,B-)rep(k,,C-)d[getx(i,j,k)]=read();
rep(i,,m)qxa[i]=read()-,qxb[i]=read()-,qya[i]=read()-,qyb[i]=read()-,qza[i]=read()-,qzb[i]=read()-,qh[i]=read();
getans(,m,);
write(ans);
return ;
}
/*
2 2 2 3
1 1 1 1 1 1 1 1
1 2 1 2 1 1 1
1 1 1 2 1 2 1
1 1 1 1 1 1 2
*/

【2018.10.18】noip模拟赛Day2 地球危机(2018年第九届蓝桥杯C/C++A组省赛 三体攻击)的更多相关文章

  1. 2018年第九届蓝桥杯C/C++A组省赛(最后一题)

    第十题 付账问题   [题目描述]    几个人一起出去吃饭是常有的事.但在结帐的时候,常常会出现一些争执.    现在有 n 个人出去吃饭,他们总共消费了 S 元.其中第 i 个人带了 ai 元.幸 ...

  2. 【蓝桥杯】2018年第九届蓝桥杯C/C++B组省赛——B题 等差素数列

    题目 标题:等差素数列 2,3,5,7,11,13,....是素数序列. 类似:7,37,67,97,127,157 这样完全由素数组成的等差数列,叫等差素数数列. 上边的数列公差为30,长度为6. ...

  3. 第九届蓝桥杯C/C++B组省赛感想

    因为做了近三年的初赛题,都对了5题+,所以这次比赛前信心满满,心里想最少水个省二没问题.可我怎么知道今年的套路居然和以前不一样了!一题深搜都没有,想想一周前做的第七届初赛题,10题有3.4题深搜题. ...

  4. 2016 第七届蓝桥杯 c/c++ B组省赛真题及解题报告

    2016 第七届蓝桥杯 c/c++ B组省赛真题及解题报告 勘误1:第6题第4个 if最后一个条件粗心写错了,答案应为1580. 条件应为abs(a[3]-a[7])!=1,宝宝心理苦啊.!感谢zzh ...

  5. 2015年第六届蓝桥杯C/C++B组省赛题目解析

    一.奖券数目 有些人很迷信数字,比如带“4”的数字,认为和“死”谐音,就觉得不吉利.虽然这些说法纯属无稽之谈,但有时还要迎合大众的需求.某抽奖活动的奖券号码是5位数(10000-99999),要求其中 ...

  6. 明码——第九届蓝桥杯C语言B组(省赛)第二题

    原创 标题:明码 汉字的字形存在于字库中,即便在今天,16点阵的字库也仍然使用广泛.16点阵的字库把每个汉字看成是16x16个像素信息.并把这些信息记录在字节中. 一个字节可以存储8位信息,用32个字 ...

  7. 2016年第七届蓝桥杯C/C++B组省赛题目解析

    题目1:煤球数目 有一堆煤球,堆成三角棱锥形.具体:第一层放1个,第二层3个(排列成三角形),第三层6个(排列成三角形),第四层10个(排列成三角形),....如果一共有100层,共有多少个煤球?请填 ...

  8. 乘积尾零——第九届蓝桥杯C语言B组(省赛)第三题

    原创 标题:乘积尾零 如下的10行数据,每行有10个整数,请你求出它们的乘积的末尾有多少个零? 5650 4542 3554 473 946 4114 3871 9073 90 4329 2758 7 ...

  9. 第几天——第九届蓝桥杯C语言B组(省赛)第一题

    原创 标题:第几天 2000年的1月1日,是那一年的第1天. 那么,2000年的5月4日,是那一年的第几天? 注意:需要提交的是一个整数,不要填写任何多余内容. 这题是送分题,只需要注意一下2000年 ...

随机推荐

  1. 目后佐道IT教育:师资团队

    高端技术顾问 1. leepoor 拥有12年的Web开发和架构经验,在阿里巴巴担任高级架构师,参与阿里巴巴基础技术平台开发和www.alibaba.com架构设计.擅长大型网站技术架构,工作中经常使 ...

  2. mysql 存在更新,不存在插入

    String sql = "insert into wb_result " + "values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,? ...

  3. Eclipse 和 MyEclipse 工程描述符

    有时候在一个Java工程里我们需要加入第三方jar包,这时你加入的最好相对路径, 而不是绝对路径.否则你的工程拿到别处就不行运行了.意思就是说你最好把相关的jar放到工程目录下. 对于Web工程来说相 ...

  4. CocoaPods 安装使用

    CocoaPods是一个负责管理iOS项目中第三方开源代码的工具.CocoaPods项目的源码在Github上管理.该项目开始于2011年8月 12日,经过一年多的发展,现在已经超过1000次提交,并 ...

  5. c++ 各种类型字符串转换

    typedef std::string u8string; u8string To_UTF8(const std::u16string &s) { std::wstring_convert&l ...

  6. jExcelAPI导入导出excel

      MS的电子表格(Excel)是Office的重要成员,是保存统计数据的一种常用格式.作为办公文档,势必要涉及到的电子文档的交换,Excel是一种在企业中非常通用的文件格式,打印和管理也比较方便.在 ...

  7. javascript设计模式(张容铭) 第14章 超值午餐-组合模式 学习笔记

    JS 组合模式更常用于创建表单上,比如注册页面可能有不同的表单提交模块.对于这些需求我们只需要有基本的个体,然后通过一定的组合即可实现,比如下面这个页面样式(如图14-2所示),我们来用组合模式实现. ...

  8. Luogu P2123 皇后游戏(贪心)

    题目链接:P2123 皇后游戏 如果证明这个题为什么是贪心的话,我是不会的,但是一看这个题目就是一个贪心,然后满足贪心的性质: 都能从两个人(东西)扩展到n个人(东西) 一定能从相邻状态扩展到不相邻的 ...

  9. OS复习

    提纲 一 操作系统的定义,各章节名词定义. 分时多道- OS四大特征,五大功能. 二 进程 创建终止挂起激活 PCB 原语:创建终止挂起激活唤醒 互斥和同步,临界资源,临界区 信号量的基础概念,受保护 ...

  10. Centos忘记密码解决方法

    centos6.8忘记root密码解决方法 重启系统后出现GRUB界面在引导装载程序菜单上,用上下方向键选择你忘记密码的那个系统键入"e" 来进入编辑模式. 接下来你可以看到如下图 ...