原题见:题目传送门
题意:给定一个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卡了半天,不懂为什么。。。先记着,以后再回来看看吧~