P8666 [蓝桥杯 2018 省 A] 三体攻击 题解
前言:本题解提供了一个做高维差分时,复杂度比容斥更优的解法,详细可以看 囧仙的日报
首先考虑修改操作,相当于在三维空间里每次指定一个立方体,对其内部点加上某个值
容易想到用三维差分维护
可以发现,每艘战舰随时间增加收到的伤害是不减的
故记答案为
这个显然可以二分答案
Q1: 为什么不一边暴力操作一边找到
A1: 因为这样复杂度会是立方体边长的立方量级,不太可接受。
Q2: 为什么一定要二分呢?
A2: 因为差分给到的是一个快速修改的手段,前缀和还原再 check 是很低效的。倘若一边修改一边 check,复杂度还不如暴力。差分+二分虽然多次重复了前面的某些修改,但它降低了 check 的次数。对于本题是一个很不错的选择。
(有点像某些分块的思想,都是平衡查询与询问的复杂度,降低整体的复杂度,但有点不一样)
Q3:不能用倍增避开重复吗?
A3:倍增跳过头的话需要一个还原的步骤,还是会"重复",而且写起来麻烦。
总而言之,我们确定了差分+二分的做法
二分一个时间
接下来就是差分板子了
但是大多数同学写的都是容斥做差分
记差分维数为
则容斥做法的复杂度为
多次差分的复杂度可以做到
(多项式两项分别为修改和查询复杂度)
(其实在
多次差分的思想大概是对着
最后做三次前缀和就合起来了
避免了容斥系数的讨论,不失为一个兼具效率与好写的做法
(具体内容在日报里有图示,不再过多展开)
贴代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e7+10;
int s1[maxn],s2[maxn],s3[maxn],ans[maxn];
int A,B,C;
inline int rd(){
int x=0;char c=getchar();int f=1;
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
inline int getid(int x,int y,int z){
return ((x-1)*B+(y-1))*C+z;
}
struct node{
int a1,b1,c1,a2,b2,c2,val;
}at[maxn];
int d[maxn];
int m;
inline void up3(int a1,int b1,int c1,int a2,int b2,int c2,int val){
s3[getid(a1,b1,c1)]+=val;
s3[getid(a1,b1,c2+1)]-=val;
return;
}
inline void up2(int a1,int b1,int c1,int a2,int b2,int c2,int val){
up3(a1,b1,c1,a1,b1,c2,val);
up3(a1,b2+1,c1,a1,b2+1,c2,-val);
return;
}
inline void up1(int a1,int b1,int c1,int a2,int b2,int c2,int val){
up2(a1,b1,c1,a1,b2,c2,val);
up2(a2+1,b1,c1,a2+1,b2,c2,-val);
return;
}
inline bool check(int T){
memset(s1,0,sizeof(s1));memset(s2,0,sizeof(s2));memset(ans,0,sizeof(ans));memset(s3,0,sizeof(s3));
for(int i=1;i<=T;i++)up1(at[i].a1,at[i].b1,at[i].c1,at[i].a2,at[i].b2,at[i].c2,at[i].val);
for(int i=1;i<=A;i++){
for(int j=1;j<=B;j++){
for(int k=1;k<=C;k++)
s2[getid(i,j,k)]=s2[getid(i,j,k-1)]+s3[getid(i,j,k)];
}
}
memset(s3,0,sizeof(s3));
for(int i=1;i<=A;i++){
for(int j=1;j<=B;j++){
for(int k=1;k<=C;k++){
s3[getid(i,j,k)]=s3[getid(i,j-1,k)]+s2[getid(i,j,k)];
}
}
}
for(int i=1;i<=A*B*C;i++)s2[i]=s3[i];
// memset(s1,0,sizeof(s1));
// for(int i=1;i<=A;i++){
// for(int j=1;j<=B;j++){
// for(int k=1;k<=C;k++){
// s1[getid(i,j,k)]=s1[getid(i,j-1,k)]+s2[getid(i,j,k)];
// }
// }
// }
// for(int i=1;i<=A*B*C;i++)s2[i]=s1[i];
for(int i=1;i<=A;i++)
for(int j=1;j<=B;j++)
for(int k=1;k<=C;k++){
s2[getid(i,j,k)]+=s2[getid(i-1,j,k)];
}
for(int i=1;i<=A;i++){
for(int j=1;j<=B;j++){
for(int k=1;k<=C;k++){
if(s2[getid(i,j,k)]>d[getid(i,j,k)])return 1;
}
}
}
return 0;
}
int main(){
A=rd();B=rd();C=rd();m=rd();
for(int i=1;i<=A*B*C;i++)d[i]=rd();
for(int i=1;i<=m;i++){
at[i].a1=rd();at[i].a2=rd();
at[i].b1=rd();at[i].b2=rd();
at[i].c1=rd();at[i].c2=rd();
at[i].val=rd();
}
int l=0,r=m+1,mid;
while(l+1!=r){
mid=l+r>>1;
if(!check(mid))l=mid;
else r=mid;
}
printf("%d\n",r);
}
彩蛋:
可能是因为数据的锅或边界问题,数组大小开到
或者要把数组开到
但它报的是 WA 而不是 RE ,这玩意我从4月调到现在才知道。。。
ps:
因为函数调用太多的问题,这玩意跑得比容斥慢(笑)
但在高维情况下这个做法优势很大,具体去看 sosdp 相关内容,不多赘述