真题解析│蓝桥杯省赛真题之三体攻击
2018年蓝桥杯软件类省赛(软件类)C/C++大学A组第7题“三体攻击”,一道程序设计题。
题目描述
2018省赛A组第7题“三体攻击” ,题目链接:
http://oj.ecustacm.cn/problem.php?id=1364
https://www.dotcpp.com/oj/problem2275.html
时间限制:2s。
内存限制:256M。
题解
(比较深入地搞过算法竞赛的队员一看就知道,这是一个三维差分的模板题。)
1. 一维差分+二分法
一维,即所有战舰排成一条线。
每次把一个区间内的所有元素(战舰生命值)减去一个相同的h t h_tht值,是经典的“一维区间修改问题”,可以用“差分数组”来处理数据。
注:“差分数组”的概念,请看罗勇军老师博文树状数组的“4. 区间修改 + 单点查询”、“5、差分数组”https://blog.csdn.net/weixin_43914593/article/details/107842628中的讲解。请先完全搞懂“差分数组”,再往下看。
“差分数组”有什么好处呢?一次修改一个区间,如果用暴力法,需要修改区间内每个元素的值,复杂度O(n),但是用差分数组,就只需要修改区间的两个端点,复杂度O(1)。m次修改的总复杂度只有O(m)。
但是光用差分数组并不能解决问题。因为在差分数组上查询区间内的每个元素是否小于0,需要用差分数组来计算区间内每个元素的值,复杂度是O(n)的。合起来的总复杂度是O(mn)的,其实跟暴力法的复杂度一样。
这就要加上第二个算法:二分法。从第1次修改到第m次修改,肯定有一次修改是临界点。在这次修改前,没有负值(战舰爆炸);在这次修改后,出现了负值,且后面一直有负值。那么对m进行二分,就能在O(logm)次内找到这个临界点,这就是答案。
具体的操作步骤是:
1、读取输入:存储n=A × B × C个点(战舰)的生命值;存储m次修改。
2、第1次二分,从最大的m开始:判断做m次修改后是否产生负值。过程是:先做m次差分修改,得到一个差分数组,复杂度O(m);然后根据这个差分数组计算每个战舰的值,看是否有负数,复杂度O(n)。总复杂度O(m+n)。
3、重复以上二分操作,直到找到临界修改的次数。
一共做O(logm)次二分,总复杂度O((m+n)logm),完美完成编码任务,AC!
2. 二维差分、三维差分
https://blog.csdn.net/weixin_44716674/article/details/105577862
https://blog.csdn.net/weixin_43738764/article/details/105553072
C++代码
下面的C++代码清晰地重现了上面的解释。如有疑问,请看注释。
//cpp文件取名为 good.cpp
#include<bits/stdc++.h>
using namespace std;
int A,B,C,n,m;
int d[1000005]; //存储舰队生命值
int D[1000005]; //三维差分数组(压维);同时也用来计算每个点的攻击值
int lat[1000005],rat[1000005]; //存储攻击
int lbt[1000005],rbt[1000005];
int lct[1000005],rct[1000005];
int ht[1000005];
int num(int i,int j,int k) { //小技巧:压维,把三维坐标[i][j][k]转为一维的((i-1)*B+(j-1))*C+(k-1)+1
if (i>A || j>B || k>C) return 0;
return ((i-1)*B+(j-1))*C+(k-1)+1;
}
bool check(int x) { //检查经过x次攻击后是否有战舰爆炸
for (int i=1; i<=n; i++) D[i]=0;
for (int i=1; i<=x; i++) { //三维差分数组:三维有8个区间端点
D[num(lat[i], lbt[i], lct[i])] += ht[i];
D[num(rat[i]+1,lbt[i], lct[i])] -= ht[i];
D[num(lat[i], rbt[i]+1,lct[i])] -= ht[i];
D[num(lat[i], lbt[i], rct[i]+1)] -= ht[i];
D[num(rat[i]+1,rbt[i]+1,lct[i])] += ht[i];
D[num(lat[i], rbt[i]+1,rct[i]+1)] += ht[i];
D[num(rat[i]+1,lbt[i], rct[i]+1)] += ht[i];
D[num(rat[i]+1,rbt[i]+1,rct[i]+1)] -= ht[i];
}
for (int i=1; i<=A; i++)
for (int j=1; j<=B; j++)
for (int k=1; k<C; k++)
D[num(i,j,k+1)] += D[num(i,j,k)]; //用差分数组计算出每个点的攻击值
for (int i=1; i<=A; i++)
for (int k=1; k<=C; k++)
for (int j=1; j<B; j++)
D[num(i,j+1,k)] += D[num(i,j,k)];
for (int j=1; j<=B; j++)
for (int k=1; k<=C; k++)
for (int i=1; i<A; i++)
D[num(i+1,j,k)] += D[num(i,j,k)];
for (int i=1; i<=n; i++)
if (D[i]>d[i])
return true; //攻击值大于生命值
return false;
}
int main() {
scanf("%d%d%d%d", &A, &B, &C, &m);
n=A*B*C;
for (int i=1; i<=n; i++) scanf("%d", &d[i]);
for (int i=1; i<=m; i++) scanf("%d%d%d%d%d%d%d",&lat[i],&rat[i],&lbt[i],&rbt[i],&lct[i],&rct[i],&ht[i]);
int L=1,R=m; //经典的二分写法
while (L<R) { //对m进行二分,找到临界值
int mid=(L+R)>>1;
if (check(mid)) R=mid;
else L=mid+1;
}
printf("%d\n", R); //打印临界值
return 0;
}
对敲和测试
正好用这个题目练练对敲和测试。
参考博文:Python在竞赛中的应用-测试数据的构造与对拍https://blog.csdn.net/weixin_43914593/article/details/111385152
1. 用python写个暴力法的对敲代码
暴力代码很容易写。下面的代码取名为baoli.py。
A,B,C,m = map(int,input().split())
ship=[]
for i in range(A):
sublist=[]
for j in range(B):
sublist.append([0]*C)
ship.append(sublist)
life=list(map(int,input().split()))
v=0
for i in range(A):
for j in range(B):
for k in range(C):
ship[i][j][k]=life[v] #战舰生命值
v += 1
num = m
for attacknum in range(1,m+1):
la, ra, lb, rb, lc, rc, ht = map(int,input().split())
for i in range(la-1,ra):
for j in range(lb-1,rb):
for k in range(lc-1,rc):
ship[i][j][k] -= ht
if ship[i][j][k]<0:
print(attacknum)
exit()
2. 用python产生测试数据
写一个py文件,按题目要求的格式产生测试数据。文件名取为test.py。
#test.py
from random import *
N = 1e4 #自定义一个合适的值
HT = 1e9
A = randint(1,N)
B = randint(1,N//A)
C = randint(1,N//A//B)
m = randint(1,N)
print( A,B,C,m) #第一行
for i in range(A*B*C-1): #第二行
print (randint(HT//1000,HT),end=' ') #生命值设大一些
print (randint(HT//1000,HT))
for i in range(m): #后面m行,每行7个
lat = randint(1,A)
rat = randint(lat,A) #注意:rat比lat大
lbt = randint(1,B)
rbt = randint(lbt,B)
lct = randint(1,C)
rct = randint(lct,C)
ht = randint(1,HT/100000) #攻击值设小一些
print (lat,rat,lbt,rbt,lct,rct,ht)
对拍测试
@echo off
set path=C:\MinGW\bin
g++ -o good.exe good.cpp
:loop
set path=C:\Users\hp\AppData\Local\Programs\Python\Python39
python test.py >data.in
good.exe <data.in >good.out
python baoli.py <data.in >py.out
set path=C:\Windows\System32
fc py.out good.out
good.exe <data.in
if errorlevel == 1 pause
goto loop
D:\cpp>aa.bat
正在比较文件 py.out 和 GOOD.OUT
FC: 找不到差异
686
正在比较文件 py.out 和 GOOD.OUT
FC: 找不到差异
995
正在比较文件 py.out 和 GOOD.OUT
FC: 找不到差异
1597
正在比较文件 py.out 和 GOOD.OUT
FC: 找不到差异
参考书籍

添加 家长论坛微信
全部 0条评论