【2018.10.18】noip模擬賽Day2 地球危機(2018年第九屆藍橋杯C/C++A組省賽 三體攻擊)


題目描述

三體人將對地球發起攻擊。為了抵御攻擊,地球人派出了 $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$,格式嘛……

 1 #include<bits/stdc++.h>
 2 #define rep(i,x,y) for(register int i=(x);i<=(y);i++)
 3 #define dwn(i,x,y) for(register int i=(x);i>=(y);i--)
 4 #define maxn 1000010
 5 #define LL long long
 6 using namespace std;
 7 int read()
 8 {
 9     int x=0,f=1;char ch=getchar();
10     while(!isdigit(ch)&&ch!='-')ch=getchar();
11     if(ch=='-')f=-1,ch=getchar();
12     while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
13     return x*f;
14 }
15 void write(int x)
16 {
17     int f=0;char ch[20];
18     if(!x){putchar('0'),putchar('\n');return;}
19     if(x<0)x=-x,putchar('-');
20     while(x)ch[++f]=x%10+'0',x/=10;
21     while(f)putchar(ch[f--]);
22     putchar('\n');
23 }
24 LL qh[maxn],a[maxn],s[maxn],d[maxn];
25 int qxa[maxn],qya[maxn],qza[maxn],qxb[maxn],qyb[maxn],qzb[maxn],A,B,C,m,ans;
26 int getx(int x,int y,int z){return (x*B+y)*C+z+1;}
27 void getp(int x,int px,int py,int pz){x--;pz=x%C,x/=C,py=x%B,pz=x/B;return;}
28 void add(int x,int y,int z,LL h){if(x<=(A-1)&&y<=(B-1)&&z<=(C-1))a[getx(x,y,z)]+=h;}
29 void atk(int xa,int ya,int za,int xb,int yb,int zb,LL h)
30 {
31     add(xa,ya,za,h),add(xa,ya,zb+1,-h),add(xa,yb+1,za,-h),add(xa,yb+1,zb+1,h);
32     add(xb+1,ya,za,-h),add(xb+1,ya,zb+1,h),add(xb+1,yb+1,za,h),add(xb+1,yb+1,zb+1,-h);
33 }
34 int check()
35 {
36     rep(i,0,A-1)rep(j,0,B-1)rep(k,0,C-1)s[getx(i,j,k)]=a[getx(i,j,k)];
37     if(C!=1)rep(i,0,A-1)rep(j,0,B-1)rep(k,1,C-1)s[getx(i,j,k)]+=s[getx(i,j,k-1)];//,cout<<i<<" "<<j<<" "<<k<<"+1 "<<s[getx(i,j,k-1)]<<endl;
38     if(B!=1)rep(i,0,A-1)rep(j,1,B-1)rep(k,0,C-1)s[getx(i,j,k)]+=s[getx(i,j-1,k)];//,cout<<i<<" "<<j<<" "<<k<<"+2 "<<s[getx(i,j-1,k)]<<endl;
39     if(A!=1)rep(i,1,A-1)rep(j,0,B-1)rep(k,0,C-1)s[getx(i,j,k)]+=s[getx(i-1,j,k)];//,cout<<<<"+3 "<<s[getx(i-1,j,k)]<<endl;
40     rep(i,0,A-1)rep(j,0,B-1)rep(k,0,C-1)if(s[getx(i,j,k)]>d[getx(i,j,k)])return 1;
41     return 0;
42 }
43 void getans(int L,int R,int lastmid)
44 {
45     if(L>R)return;
46     int mid=(L+R)>>1;
47     if(mid>lastmid)rep(i,lastmid+1,mid)/*cout<<"mid:"<<mid<<" add:"<<i<<endl,*/atk(qxa[i],qya[i],qza[i],qxb[i],qyb[i],qzb[i],qh[i]);
48     if(mid<lastmid)rep(i,mid+1,lastmid)/*cout<<"mid:"<<mid<<" del:"<<i<<endl,*/atk(qxa[i],qya[i],qza[i],qxb[i],qyb[i],qzb[i],-qh[i]);
49     int f=check();
50     //cout<<"mid:"<<mid;
51     //cout<<"now:";
52     //rep(i,0,A-1)rep(j,0,B-1)rep(k,0,C-1)cout<<s[getx(i,j,k)]<<" ";cout<<endl;
53     if(f){ans=min(ans,mid);getans(L,mid-1,mid);}
54     else getans(mid+1,R,mid);
55 }
56 int main()
57 {
58     //freopen("attack.in","r",stdin);
59     //freopen("attack.out","w",stdout);
60     A=read(),B=read(),C=read(),ans=m=read();
61     rep(i,0,A-1)rep(j,0,B-1)rep(k,0,C-1)d[getx(i,j,k)]=read();
62     rep(i,1,m)qxa[i]=read()-1,qxb[i]=read()-1,qya[i]=read()-1,qyb[i]=read()-1,qza[i]=read()-1,qzb[i]=read()-1,qh[i]=read();
63     getans(1,m,0);
64     write(ans);
65     return 0;
66 }
67 /*
68 2 2 2 3
69 1 1 1 1 1 1 1 1
70 1 2 1 2 1 1 1
71 1 1 1 2 1 2 1
72 1 1 1 1 1 1 2
73 */
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM