liu_runda 給辣雞蒟蒻做的 NOIP模擬賽 1.0 第二題 任(duty) 題解
問題 B: 任(duty)
時間限制: 2 Sec 內存限制: 512 MB題目描述
liu_runda退役之後就失去夢想開始鹹魚生活了…
Bilibili夏日畫板活動中,所有人都可以在一塊畫板上進行像素畫創作.UOJ群有一群無聊的人決定在畫板上創作一個50*50的UOJ的LOGO.如下圖.
這塊畫板實際上是很大的矩形網格.一個網格是一像素.
一個人每三分鐘才能畫一個像素.所以liu_runda的鹹魚生活非常無聊.
郭神表示他實在是看不下去liu_rudna這只頹狗了,於是隨手出了一道神題,liu_runda不會做,於是給出到聯考裏了.
在畫板上有一片黑白相間的矩形區域滿足這樣的性質:如果認為相同顏色的方塊可以在上下左右四個方向連通,那麽任意兩個黑色方塊要麽不連通,要麽連通但之間只有一條簡單路徑(不重復經過同一個格子的路徑).
這個矩形區域有N行M列,從上到下依次為第1,2,3…N-1,N行,從左到右依次為第1,2,3…M-1,M列.
每次郭神會詢問這片矩形區域內的一個子矩形.在只考慮這個子矩形內的像素時(即從子矩形內部不能和子矩形之外的像素相連通),問這個子矩形內的黑色方塊組成了多少連通塊.
如果不能完成這個任務,liu_runda就會被郭神批判一番…
【輸入格式】
第一行三個整數N,M,Q,表示矩形區域有N行M列,有Q組詢問.
接下來N行,每行一個長為M的01字符串.0表示白色,1表示黑色.第i行第j個字符表示第i行j列的顏色,
接下來Q行,每行4個整數x1,y1,x2,y2,(x1<=x2,y1<=y2)表示選出的矩形區域的兩個對角.即選出一個左上角為第x1行第y1列,右下角為第x2行第y2列,包含x2-x1+1行,y2-y1+1列的區域.
【輸出格式】
Q行,第i行一個整數ans表示第i組詢問的答案.
【樣例輸入1】
3 4 4
1101
0110
1101
1 1 3 4
1 1 3 1
2 2 3 4
1 2 2 4
【樣例輸出1】
3
2
2
2
【樣例輸入2】
5 5 6
11010
01110
10101
11101
01010
1 1 5 5
1 2 4 5
2 3 3 4
3 3 3 3
3 1 3 5
1 1 3 4
【樣例輸出2】
3
2
1
1
3
2
【數據範圍】
對於第1,2個測試點,Q=1
對於第3,4個測試點,N=1
對於第5,6,7個測試點,N=2
對於第8個測試點,N,M<=1000
對於第9個測試點,N,M<=1500
對於全部測試點,1<=N,M<=2000,1<=Q<=200000,1<=x1<=x2<=N,1<=y1<=y2<=M,保證任意兩個黑色像素之間最多只有一條簡單路徑.
這道題語文負分的我讀了三遍題,手推了兩個樣例才明白到底要幹什麽,耽誤了10分鐘。
做完第一題就發現這次和之前完全不一樣,是標準的NOIP模擬題,然後現在心中默默盤算了一下,這道題應該是需要拿60++,AC最好。然後就開始考慮對策。
看到數據範圍之後很明顯,我們必須要搞到最基本的70分,在加上第三題暴力20分,190分就可以保底了。
首先第1,2個點,很明顯,暴力bfs打一會就拿到20分了。然後是N=1這兩個點,當時想到的是前綴和思想,畢竟就一條格子,我們只要維護一個前綴和記錄該點即其之前的格子有多少個聯通塊,如果x1,y1為黑塊就把兩個前綴和的差+1就是答案了,否則直接輸出前綴和的差。然後打完N=1之後就開始去想N=2,想了一會發現對於不跨行的和之前同理,跨行的只要處理方式變化一下其他和之前沒有任何變化就好了。就這樣70分Get。
1 #include<iostream> 2 #include<cstdlib> 3 #include<cstdio> 4 #include<cstring> 5 #include<queue> 6 #include<algorithm> 7 #include<cmath> 8 #include<map> 9 #define N 2005 10 using namespace std; 11 int a[N][N],n,m,q,x11,x2,y11,y2,zy[10][5],sum[N][N]; 12 bool fw[N][N]; 13 char b[N]; 14 bool fro[N][N][5]; 15 bool check(int x,int y) 16 { 17 if(!a[x][y]||x<x11||x>x2||y<y11||y>y2)return 0; 18 return 1; 19 } 20 struct inf 21 { 22 int x,y; 23 }; 24 void bfs(int xx,int yy) 25 { 26 queue<inf> q1; 27 inf aa; 28 aa.x=xx,aa.y=yy; 29 q1.push(aa); 30 fw[xx][yy]=1; 31 while(!q1.empty()) 32 { 33 inf tt=q1.front();q1.pop(); 34 int x=tt.x,y=tt.y; 35 for(int i=1;i<=4;i++) 36 { 37 int tx=x+zy[i][1],ty=y+zy[i][2]; 38 if(!check(tx,ty))continue; 39 if(fw[tx][ty]) 40 { 41 if(i==1)fro[tx][ty][3]=1; 42 if(i==2)fro[tx][ty][4]=1; 43 if(i==3)fro[tx][ty][1]=1; 44 if(i==4)fro[tx][ty][2]=1; 45 continue; 46 } 47 fw[tx][ty]=1; 48 inf bb; 49 bb.x=tx,bb.y=ty; 50 q1.push(bb); 51 } 52 } 53 } 54 void work1() 55 { 56 zy[1][1]=-1,zy[2][2]=-1,zy[3][1]=1,zy[4][2]=1; 57 for(int i=1;i<=q;i++) 58 { 59 int ans=0; 60 memset(fw,0,sizeof(fw)); 61 scanf("%d%d%d%d",&x11,&y11,&x2,&y2); 62 for(int j=x11;j<=x2;j++) 63 { 64 for(int k=y11;k<=y2;k++) 65 { 66 if(!a[j][k]||fw[j][k])continue; 67 ans++; 68 bfs(j,k); 69 } 70 } 71 printf("%d\n",ans); 72 } 73 } 74 void work2() 75 { 76 int sum[N]; 77 memset(sum,0,sizeof(sum)); 78 for(int i=1;i<=m;i++) 79 { 80 sum[i]=sum[i-1]; 81 if(a[1][i]&&!a[1][i-1])sum[i]++; 82 } 83 for(int i=1;i<=q;i++) 84 { 85 scanf("%d%d%d%d",&x11,&y11,&x2,&y2); 86 int ans=sum[y2]-sum[y11]+a[x11][y11]; 87 printf("%d\n",ans); 88 } 89 } 90 void work3() 91 { 92 int sum[N][3]; 93 memset(sum,0,sizeof(sum)); 94 for(int i=1;i<=2;i++) 95 { 96 for(int j=1;j<=m;j++) 97 { 98 sum[j][i]=sum[j-1][i]; 99 if(a[i][j]&&!a[i][j-1])sum[j][i]++; 100 } 101 } 102 for(int i=1;i<=m;i++) 103 { 104 sum[i][0]=sum[i-1][0]; 105 if(a[1][i]||a[2][i]) 106 { 107 if(a[1][i]&&a[2][i]) 108 { 109 if((!a[1][i-1])&&(!a[2][i-1])) 110 sum[i][0]++; 111 } 112 else if(a[1][i]&&(!a[2][i])) 113 { 114 if(!a[1][i-1]) 115 sum[i][0]++; 116 } 117 else 118 { 119 if(!a[2][i-1]) 120 sum[i][0]++; 121 } 122 } 123 } 124 for(int i=1;i<=q;i++) 125 { 126 scanf("%d%d%d%d",&x11,&y11,&x2,&y2); 127 if(x11==x2) 128 printf("%d\n",sum[y2][x2]-sum[y11][x11]+a[x11][y11]); 129 else 130 { 131 int ju; 132 if(a[x11][y11]||a[2][y11])ju=1; 133 else ju=0; 134 printf("%d\n",sum[y2][0]-sum[y11][0]+ju); 135 } 136 } 137 } 138 int main() 139 { 140 scanf("%d%d%d",&n,&m,&q); 141 for(int i=1;i<=n;i++) 142 { 143 scanf("%s",b+1); 144 for(int j=1;j<=m;j++) 145 { 146 a[i][j]=b[j]-‘0‘; 147 } 148 } 149 if(q==1) 150 { 151 work1(); 152 } 153 else if(n==1) 154 { 155 work2(); 156 } 157 else if(n==2) 158 { 159 work3(); 160 } 161 else 162 { 163 for(int i=1;i<=n;i++) 164 { 165 for(int j=1;j<=n;j++) 166 { 167 if(!fw[i][j]&&a[i][j]) 168 { 169 bfs(i,j); 170 } 171 } 172 } 173 for(int i=1;i<=n;i++) 174 { 175 int f=0; 176 bool la=1; 177 for(int j=1;j<=m;j++) 178 { 179 sum[i][j]=sum[i-1][j]; 180 if(a[i][j]) 181 { 182 if(!a[i][j-1]&&!a[i-1][j]&&la)f++,la=1; 183 else if(a[i-1][j]&&a[i][j-1]&&la)f--,la=0; 184 } 185 else la=1; 186 sum[i][j]+=f; 187 } 188 } 189 for(int i=1;i<=q;i++) 190 { 191 scanf("%d%d%d%d",&x11,&y11,&x2,&y2); 192 int ans=sum[x2][y2]+sum[x11-1][y11-1]-sum[x11-1][y2]-sum[x2][y11-1]; 193 int la=0; 194 for(int j=x2;j>x11;j--) 195 { 196 if(a[j][y11]&&!fro[j][y11][4]&&fro[j][y11][1]&&!la)ans++,la=1; 197 else la=0; 198 } 199 200 if(a[x11][y11]) 201 { 202 if(!la&&(fro[x11][y11][1]||fro[x11][y11][2]))ans++,la=1; 203 else la=0; 204 }else la=0; 205 for(int j=y11+1;j<y2;j++) 206 { 207 if(a[x11][j]&&!fro[x11][j][1]&&fro[x11][j][2]&&!la)ans++,la=1; 208 else la=0; 209 } 210 if(a[x11][y2]) 211 { 212 if(!fro[x11][y2][1]&&!fro[x11][y2][4]&&fro[x11][y2][2]&&!la)ans++; 213 } 214 printf("%d\n",ans); 215 } 216 } 217 return 0; 218 }70分打法(請自動忽略後半部分)
然後先去打第三題暴力去了,大概過了30分鐘吧(當然,這段時間裏還想了一會第三題正解),又回來做這道題。
回來之後開始聯想NOIP試題套路,然後就想到了位於同樣位置的“天天愛跑步”,那道題是通過部分分打法啟發選手打出正解,那麽這道題是不是呢?當時其實想岔了,因為我之前維護的前綴和就是聯通塊個數,於是乎我就傻乎乎的維護了一個至今還不知道對不對的求聯通塊的個數的二維數組,然後對於單個詢問還是O(m+n)的復雜度出理,連可以O(n*m)預處理都忘了。果斷T掉,結果一開始還在撤銷的時候少打一下,險些編譯錯誤,還好之後又發現了,有驚無險……要是聯賽犯這種錯誤我肯定想死的心都有了。
其實,當時對於正解思路還有另一個想法,就是二維樹狀數組,因為如果O(q*log(n*m))貌似也是可行的。只不過由於N=1,N=2部分分中沒用到所以否決了這個想法。
關於考試說完了,下面說正解。
其實當時我推理推對了一半,的確和部分分的前綴和有關,不過我們A掉這道題首先得先想到一件事,對於一個無環圖中,聯通塊個數等於點數-邊數,這也就是為什麽輸入數據要保證這個奇怪的簡單路徑(當時我註意到了這點,但是也沒有去想太多,只是顧著去想正解了)。所以我們只需要維護四個前綴和,點數,邊數,橫向連邊數,縱向連邊數然後O(1)輸出。
1 #include<iostream> 2 #include<cstdlib> 3 #include<cstdio> 4 #include<cstring> 5 #include<queue> 6 #include<algorithm> 7 #include<cmath> 8 #include<map> 9 #define N 2005 10 using namespace std; 11 int a[N][N],n,m,q,x11,x2,y11,y2,sum[N][N],sum2[N][N],sum3[N][N],sum4[N][N]; 12 char b[N]; 13 int main() 14 { 15 scanf("%d%d%d",&n,&m,&q); 16 for(int i=1;i<=n;i++) 17 { 18 scanf("%s",b+1); 19 for(int j=1;j<=m;j++) 20 { 21 a[i][j]=b[j]-‘0‘; 22 } 23 } 24 for(int i=1;i<=n;i++) 25 { 26 for(int j=1;j<=m;j++) 27 { 28 sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]; 29 } 30 } 31 for(int i=1;i<=n;i++) 32 { 33 int f=0,t=0; 34 for(int j=1;j<=m;j++) 35 { 36 if(a[i][j]&&a[i][j-1]) f++; 37 if(a[i][j]&&a[i-1][j]) f++,t++; 38 sum2[i][j]=sum2[i-1][j]+f; 39 sum3[i][j]=t; 40 } 41 } 42 for(int i=1;i<=m;i++) 43 { 44 int f=0; 45 for(int j=1;j<=n;j++) 46 { 47 if(a[j][i]&&a[j][i-1]) f++; 48 sum4[j][i]=f; 49 } 50 } 51 for(int i=1;i<=q;i++) 52 { 53 scanf("%d%d%d%d",&x11,&y11,&x2,&y2); 54 printf("%d\n",sum[x2][y2]-sum[x11-1][y2]-sum[x2][y11-1]+sum[x11-1][y11-1]-(sum2[x2][y2]-sum2[x11-1][y2]-sum2[x2][y11-1]+sum2[x11-1][y11-1]-(sum3[x11][y2]-sum3[x11][y11-1])-(sum4[x2][y11]-sum4[x11-1][y11]))); 55 } 56 return 0; 57 }真.AC代碼 對於這道題,遺憾說實在的,並不是太大,畢竟我不記得這個結論了,但如果我記得這個結論我可以保證我可以A掉,但沒有如果……
liu_runda 給辣雞蒟蒻做的 NOIP模擬賽 1.0 第二題 任(duty) 題解