原题见:题目传送门
题意:给定一个n*m(1<=n,m<=1000)的矩阵白块,然后输入q(1<=q<=1e4)对结点,每对结点在同一行或者同一列(两个结点连成的线段上的结点也被连接起来了),涂黑这些结点对连线上的所有白色结点(包括两端点)。每次涂黑后都要求输出此时矩阵中白色联通块的数量
(额。。。感觉总结能力完全不行,还不如直接看题。。。。)
输入:
第一行:n m q
往下q行:x1 y1 x2 y2
(x1,y1)与(x2,y2)就是即将涂黑的俩端点,输入保证x1==x2或者y1==y2
输出:q行,每次涂黑后的矩阵中的白色联通块数量
时间限制:4s
简单分析一波:常规思维是输入一组,便求一次白色联通块数量,然而,随便算一下复杂度吧,DFS或者BFS求联通块,复杂度差不多是n*m,然后q次查询,也就是O(n*m*q),大概是1e3*1e3*1e4=1e10。4s?不存在的~然后,不擅长做图论题的我就不会了——但是我有队友啊——这货这一题做第三次了。。。
清真分析一波:既然正向思考,输入一次,计算一次不行的话,那就逆向算喽~记下每一次连接的两个端点,标记第一次被涂黑的时间戳,也就是第几次它被涂黑了~然后我们直接算最后一次有多少个白色联通块,这一步复杂度是O(n*m),然后反向一次次地涂白,看每次涂白时是否构成了新的联通块,或者涂白后是否将不同联通块连接在了一起,这一步复杂度最坏是O(m)或者O(n),二者范围一样,都是1e3,就以O(n)算,q次,也就是O(n*q),这样总的复杂度也就是O(n*m+n*q),也就1e7数量级的复杂度,完美~
然后就是代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<queue> #include<cstring> using namespace std; struct point { int x, y; point(int X = 0, int Y = 0):x(X), y(Y) {}; void read() { scanf("%d%d", &x, &y); } bool operator==(const point& c) const { return x == c.x && y == c.y; } };//结构体:点 struct line { point x, y; void read() { x.read(); y.read(); } }h[10010];//结构体:两个点,记录每一次涂黑的两个端点 point dad[1001][1001];//记录每一个点的父节点 point father(point p)//更新父节点 { if(p == dad[p.x][p.y]) return p; return dad[p.x][p.y] = father(dad[p.x][p.y]); } int t[1001][1001]; //记录每一个结点第一次被涂黑的时间戳 int cnt[10010]; //记录每一次查询的联通块数量 bool used[1001][1001]; //BFS求联通块时判断(i,j)是否被访问过 int main() { #ifdef AFei freopen("in.txt", "r", stdin); #endif // AFei int n, m, q; scanf("%d%d%d", &n, &m, &q); memset(t, -1, sizeof t);// t[i][j]==-1表示(i,j)点是白的~ for(int i = 0; i < q; ++ i) { h[i].read();//读入每次涂黑的线段的两个端点 int xl = min(h[i].x.x, h[i].y.x)-1, xr = max(h[i].x.x, h[i].y.x)-1; int yl = min(h[i].x.y, h[i].y.y)-1, yr = max(h[i].x.y, h[i].y.y)-1; for(int j = xl; j <= xr; ++ j)//记录某一点在哪一次被涂黑的 for(int k = yl; k <= yr; ++ k) if(t[j][k] == -1) t[j][k] = i; } for(int i = 0; i < n; ++ i) for(int j = 0; j < m; ++ j) { dad[i][j] = point(i, j);//初始认为每一个点都是一个联通块,它们以自己为父节点 } //求最终联通块数量 cnt[q-1] = 0; int row[4] = {0, 0, 1, -1}; int col[4] = {1, -1, 0, 0}; for(int i = 0; i < n; ++ i) { for(int j = 0; j < m; ++ j) { if(t[i][j] == -1 && father(point(i, j)) == point(i, j))//BFS找联通块,整个联通块的父结点都是(i,j) { point d = point(i, j); cnt[q-1] ++; queue<point> Q; Q.push(d); while(!Q.empty()) { point next = Q.front(); Q.pop(); for(int k = 0; k < 4; ++ k) { int nX = next.x + row[k], nY = next.y + col[k]; if(nX >= 0 && nX < n && nY >= 0 && nY < m && used[nX][nY] == 0 && t[nX][nY] == -1) {//在范围内的点,并且没被访问过,并且是白块的点 used[nX][nY] = 1; Q.push(point(nX, nY)); dad[nX][nY] = point(i, j); } } } } } } for(int i = q-1; i; -- i)//逆向求解 { cnt[i-1] = cnt[i];//cnt[i]就是第i次查询的解,i从0开始 int xl = min(h[i].x.x, h[i].y.x)-1, xr = max(h[i].x.x, h[i].y.x)-1; int yl = min(h[i].x.y, h[i].y.y)-1, yr = max(h[i].x.y, h[i].y.y)-1; for(int j = xl; j <= xr; ++ j) for(int k = yl; k <= yr; ++ k) { if(t[j][k] == i)//如果(j,k)是在第i次涂黑的 { t[j][k] = -1;//强行涂白 point P[4] = {point(-1, -1), point(-1, -1), point(-1, -1), point(-1, -1)}; //记录(j,k)上下左右的点的父节点, bool flag = 0;//判断四周是不是都是黑块 //上下左右只要有一个方向是白块,就将(j,k)与那个白块所属的联通块相连通 if(j && t[j-1][k] == -1) flag = 1, dad[j][k] = P[0] = father(point(j-1, k)); if(k && t[j][k-1] == -1) flag = 1, dad[j][k] = P[1] = father(point(j, k-1)); if(j < n-1 && t[j+1][k] == -1) flag = 1, dad[j][k] = P[2] = father(point(j+1, k)); if(k < m-1 && t[j][k+1] == -1) flag = 1, dad[j][k] = P[3] = father(point(j, k+1)); if(!flag)//如果四周都是黑块,那么联通块+1 cnt[i-1] ++; else { for(int z = 0; z < 4; ++ z)//遍历四个方向 { if(P[z] == point(-1, -1))//如果这个方向不是白块儿 continue; if(!(father(P[z]) == father(point(j, k))))//两个不同的联通块由于(j,k)被涂白而相连通 { cnt[i-1] --;//总联通块数量-1 dad[P[z].x][P[z].y] = father(point(j, k));//更新父节点 } } } } } } for(int i = 0; i < q; ++ i) printf("%d\n", cnt[i]); return 0; }
在135行,if(!(father(P[z]) == father(point(j, k))))
我一开始写的是if(!(P[z] == father(point(j, k))))
然后就wa了,这bug卡了半天,不懂为什么。。。先记着,以后再回来看看吧~