JZOJ100048 【NOIP2017提高A組模擬7.14】緊急撤離
題目
題目大意
給你一個01矩陣,每次詢問從一個點是否可以走到另一個點。
每次走只能往右或者往下。
思考歷程
這題啊,我想的時候真的是腦洞大開……
首先,我一眼看下去,既然要詢問是否聯通,那麼能不能求出它們的最短路,看看是不是它們的曼哈頓距離?
看到資料範圍之後這個想法徹底涼涼……
然後就開始考慮一些正經的方法……
首先,考慮如何掃描線……類似掃描線的,掃一掃,維護一下,說不定就可以了呢?
然後,我發現無論如何,我都難以逃脫
,就算是使用bitset
也不行。
這樣不行啊,我就考慮分治?
如何分治?
每次在中間的這一條線上開始,向兩邊進行轉移。轉移什麼?轉移每個點到這條線上連通的狀態……
用bitset
但是又感覺,這個方法好像過不去,所以,我又繼續向其他的做法。
然後就想到了分塊!
如何分塊呢?設塊的行數為 ,那麼我們在每個塊的邊界那裡,搞一搞類似上面的轉移。
然後我們在所有的邊界之間,求出它們的連通性。
推了一波複雜度,好想很優秀!
好開心好開心……
然後打了幾行,突然發現:時間複雜度好像算錯了!
非常不爽,又算了一遍。發現還是算錯了,再算一遍……
什麼,這麼慢,連分治都不如?
又看看時間,似乎不多了……
我絕望地打了個暴力,用
bitset
正解
其實正解在比賽時已經想到了。
只不過覺得過不了……
這題的正解就是分治,和上面說的一模一樣!
非常不爽……
這次說詳細一些:
按行或列分治(其實應該按列分治,具體原因……),下面一行為準。
我們將矩陣分成上下兩個部分。
對於上面,我們設
表示點
到中間的這一行上每個點的狀態。
對於下面,我們設
表示中間這一行上的每個點到
的狀態。
其實兩個是相反的,具體怎麼轉移顯然。
那麼對於詢問的兩個點,如果它們之間的路徑上會經過這一行,那就列舉經過行上的哪一個點,計算一下是否聯通就好了。至於沒有經過的,直接遞迴分治下去。
然後分析一下時間複雜度。
首先我們知道,很顯然的,分治只有
層。
對於每一層,我們需要處理
次,因為我們考慮同一列上的點,它們轉移所耗費的時間為
,即為整個平面。由於每一行有
個東西,所以就是
次。
綜上,轉移的總時間是
。
然後就是處理詢問的時間。
我們首先將詢問全部列在一起,然後在處理的時候,將左邊的區間放左邊,將右邊的區間放右邊,穿過中間行的區間直接處理。時間複雜度可以這麼理解:對於每一個詢問,它相當於在這棵分治所形成的的二叉樹上面往下走,那麼每個的時間為
。然後我們一共有
個詢問,所以時間複雜度為
。還有每次處理一個詢問都需要
的時間
為什麼時間複雜度好像和題解不一樣,難道是我分析錯了?還是
太小以至於題解不屑於注意?
綜上所述,時間複雜度是
。
這個時間複雜度似乎過不去,然而,由於有bitset
優化,會快很多。
這題的正解就是這麼簡單……
資料上的問題
我要吐槽一下,這題的資料太可惡了!
我打出來之後,發現自己TLE!怎麼可能?
然後,就是瘋狂的卡常數歷程……最終以990+的好時間卡了過去。
我不禁深思,為什麼我的程式這麼慢?我是不是該重修卡常技能?
看看別人的程式,似乎沒有什麼特別的地方啊!
在我絕望之際,忽然,我發現了驚天的祕密!
為什麼他們是按列分治的?我翻遍所有的程式,發現AC的都是按列分治的。有一個按行分治的人過了,但開了O2。
按理來說,按列分治和按行分治的時間複雜度是一樣的。因為它們本質上都是同一個道理。
並且,在列舉的過程中,按行分治和按列分治在常數上的差異其實不大。
(我們可以想一想,不管是按行分治還是按列分治,都是將矩陣分為兩個部分。這兩個部分都是一個矩形,從右下角列舉到左上角,所以說常熟還是差不多的。卡過常數的人們都知道,在列舉的過程中,儘量一個接一個地列舉,不要跳著來。可問題是,都是一個接一個地列舉啊!)
所以說,原因只能歸結於資料。
資料害死人!!!
然後我就腦補了一下出資料的場景:
出題人:這題要卡常才能過!所以我的資料要出大一些!
然後出了各種大資料,將標程可以過的留下來……
然而標程是按列分治的,並沒有按行分治的,所以按行分治的不一定能過……
這隻能怪出題人了。
另外的吐槽
我還發現,這題可以鍛鍊我的卡常技巧!
如何在卡常的情況下,依然能保持程式的美觀?
~~眾所周知,~~我的程式是很美觀的……
然後請看看我的程式碼。
程式碼
這程式碼可能有點神仙,畢竟,美觀與常數不可兼得!
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 500
#define Q 600000
struct bitset{/手打bitset
unsigned long long b[8];
inline void reset(){//清空
memset(b,0,sizeof b);
}
inline bool operator[](int y){//查詢某一個位上的值
return b[y>>6]>>(y&63)&1;
}
inline void set(int y){
b[y>>6]|=1ll<<(y&63);//將某一個位上的值的值賦為1
}
inline void getor(bitset *ano){//將當前的bitset和其它bitset取or賦給自己
b[0]|=ano->b[0];
b[1]|=ano->b[1];
b[2]|=ano->b[2];
b[3]|=ano->b[3];
b[4]|=ano->b[4];
b[5]|=ano->b[5];
b[6]|=ano->b[6];
b[7]|=ano->b[7];
}
inline void get(bitset *x,bitset *y){//將自己的值賦為兩個bitset的or值
b[0]=x->b[0]|y->b[0];
b[1]=x->b[1]|y->b[1];
b[2]=x->b[2]|y->b[2];
b[3]=x->b[3]|y->b[3];
b[4]=x->b[4]|y->b[4];
b[5]=x->b[5]|y->b[5];
b[6]=x->b[6]|y->b[6];
b[7]=x->b[7]|y->b[7];
}
} bs[N*N+1];
int cnt;
inline int input(){//讀入優化
char ch=getchar();
while (ch<'0' || '9'<ch)
ch=getchar();
int res=0;
do{
res=res*10+ch-'0';
ch=getchar();
}
while ('0'<=ch && ch<='9');
return res;
}
int n,m;
char mat[N+1][N+1];
int q;
struct Question{
int a,b,c,d;
int num;
} _t[Q+1],_tmp[Q+1],*t,*tmp;
bitset *f[N][N],*g[N][N];//為什麼用指標呢?因為我們很容易發現,有時只會從一個轉移,那麼指標就可以大大地加快速度。具體見下面。
void dfs(int,int,int,int);
bool ans[Q+1];
int main(){
n=input(),m=input();
for (int i=0;i<n;++i)
scanf("%s",mat[i]);
q=input();
int tmpq=q;
q=0;
t=_t,tmp=_tmp;
for (int i=1;i<=tmpq;++i){
++q;
t[q].a=input()-1,t[q].b=input()-1,t[q].c=input()-1,t[q].d=input()-1;
t[q].num=i;
if (t[q].a>t[q].c || t[q].b>t[q].d)
q--;
}
dfs(0,n-1,1,q);
for (int i=1;i<=q;++i)
if (ans[i])
printf("Safe\n");
else
printf("Dangerous\n");
return 0;
}
void dfs(int l,int r,int x,int y){//[l,r]表示行的區間,[x,y]表示詢問的區間
if (l>r || x>y)
return;
int mid=l+r>>1;
cnt=0;
if (mat[mid][m-1]=='0'){
bs[++cnt].reset();
bs[cnt].set(m-1);
f[mid][m-1]=bs+cnt;
}
else
f[mid][m-1]=bs;
for (int i=m-2;i>=0;--i)
if (mat[mid][i]=='0'){
++cnt;
bs[cnt].reset();
bs[cnt].set(i);
if (mat[mid][i+1]=='0')
bs[cnt].getor(f[mid][i+1]);
f[mid][i]=bs+cnt;
}
else
f[mid][i]=bs;
for (int i=mid-1;i>=l;--i)
if (mat[i][m-1]=='0' && mat[i+1][m-1]=='0')
f[i][m-1]=f[i+1][m-1];
else
for (;i>=l;--i)
f[i][m-1]=bs;//在轉移最後一列的時候,我們發現,如果當中有一個斷了,後面的就全斷了
for (int i=mid-1;i>=l;--i)
for (int j=m-2;j>=0;--j)
if (mat[i][j]=='0')
if (mat[i][j+1]=='0'){
if (mat[i+1][j]=='0'){
bs[++cnt].get(f[i][j+1],f[i+1][j]);
f[i][j]=bs+cnt;
}
else
f[i][j]=f[i][j+1];
}
else{
if (mat[i+1][j]=='0')
f[i][j]=f[i+1][j];
else
f[i][j]=bs;
}
if (mat[mid][0]=='0'){
bs