有趣的二维前缀和
题目描述 = 略
题目大意
一个用01矩阵表示的图中,如果两个1号格子相邻则表示它们相互连通,其中保证连通的1号格子无环存在。每一次询问一个子矩阵中的连通块个数。
对于第 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
题目分析
基础暴力做法
本题的部分分给的还是非常良心的。我们可以试一试对于每一次的询问都做一次洪水填充。那么复杂度大概是O(nmq)的。
要注意一点,如果每一次询问时都把vis数组memset,会TLE得只剩20分。我们只要每一次染不同的颜色就好了。
1 #include<bits/stdc++.h> 2 const int maxn = 2003; 3 const int dx[] = {0, 1, 0, -1, 0}; 4 const int dy[] = {0, 0, 1, 0, -1}; 5 6 int n,m,q,a,b,c,d; 7 int mp[maxn][maxn]; 8 int vis[maxn][maxn]; 9 10 inline int getint() 11 { 12 char ch = getchar(); 13 for (; !isdigit(ch); ch = getchar()); 14 return ch-'0'; 15 } 16 inline int read() 17 { 18 int num = 0; 19 char ch = getchar(); 20 for (; !isdigit(ch); ch = getchar()); 21 for (; isdigit(ch); ch = getchar()) 22 num = (num<<1)+(num<<3)+ch-48; 23 return num; 24 } 25 void dfs(int x, int y, int col) 26 { 27 if (x < a || x > c || y < b || y > d || vis[x][y]==col || !mp[x][y]) return; 28 vis[x][y] = col; 29 for (int i=1; i<=4; i++) 30 dfs(x+dx[i], y+dy[i], col); 31 } 32 int main() 33 { 34 n = read(), m = read(), q = read(); 35 register int ans,i,j,k; 36 for (i=1; i<=n; i++) 37 for (j=1; j<=m; j++) 38 mp[i][j] = getint(); 39 for (i=1; i<=q; i++) 40 { 41 a = read(), b = read(), c = read(), d = read(); 42 ans = 0; 43 for (j=a; j<=c; j++) 44 for (k=b; k<=d; k++) 45 if (vis[j][k]!=i && mp[j][k]) 46 dfs(j, k, i),ans++; 47 printf("%d\n",ans); 48 } 49 return 0; 50 }
期望得分 = 实际得分 = 70分。
要一些结论的做法
这种题型————二维的计数问题————大概算是一种套路吧。我们考虑一下二维的前缀和。
仔细观察一下(虽然这挺不显然的),会发现对于无环图,一个矩形区域内的连通块个数=点数-边数。
诶,那么有了这个性质再二维前缀和就非常方便了。
注意横边和竖边是要分开保存的。
一开始我非常奇怪为什么边不保存在一起。后来发现是因为:相同一个区域内的横边和竖边,在数组中被保存的二维区间是不一样的。
1 #include<bits/stdc++.h> 2 const int maxn = 2003; 3 4 int f[maxn][maxn],mp[maxn][maxn]; 5 int egs1[maxn][maxn],egs2[maxn][maxn]; 6 int n,m,q; 7 8 inline int getint() 9 { 10 char ch = getchar(); 11 for (; !isdigit(ch); ch = getchar()); 12 return ch-'0'; 13 } 14 inline int read() 15 { 16 char ch = getchar(); 17 int num = 0; 18 for (; !isdigit(ch); ch = getchar()); 19 for (; isdigit(ch); ch = getchar()) 20 num = (num<<1)+(num<<3)+ch-48; 21 return num; 22 } 23 int main() 24 { 25 n = read(), m = read(), q = read(); 26 for (int i=1; i<=n; i++) 27 for (int j=1; j<=m; j++) 28 mp[i][j] = getint(),f[i][j] = f[i-1][j]+f[i][j-1]-f[i-1][j-1]+mp[i][j]; 29 for (int i=1; i<=n; i++) 30 for (int j=1; j<=m; j++) 31 { 32 egs1[i][j] = egs1[i-1][j]+egs1[i][j-1]-egs1[i-1][j-1]; 33 egs2[i][j] = egs2[i-1][j]+egs2[i][j-1]-egs2[i-1][j-1]; 34 if (!mp[i][j]) continue; 35 if (mp[i][j-1]) egs1[i][j]++; 36 if (mp[i-1][j]) egs2[i][j]++; 37 } 38 for (int i=1; i<=q; i++) 39 { 40 int a = read(), b = read(), c = read(), d = read(); 41 int ans = f[c][d]+f[a-1][b-1]-f[a-1][d]-f[c][b-1]; 42 ans -= egs1[c][d]+egs1[a-1][b]-egs1[a-1][d]-egs1[c][b]; //这里,可以看到横边和竖边的操作是不同的。 43 ans -= egs2[c][d]+egs2[a][b-1]-egs2[a][d]-egs2[c][b-1]; 44 printf("%d\n",ans); 45 } 46 return 0; 47 }
END