题目描述
“为你写诗,为你静止,为你做不可能的事”,爱情是一种怪事,它让奎奎开始学习画画。奎奎认为一张画的艺术价值等于画上的白色联通块个数(当一个格子和它上下左右四个方向上的某个相邻格子颜色相同,则认为它们属于同一个联通块),奎奎还认为他作画的艺术价值和妹子对他的好感度紧密相关,因此奎奎非常在意每一时刻他的画的艺术价值。 为了简化题目,奎奎在一张n行m列的白色矩形格子画布上作画,他一共画了q笔,每一笔都是从(x1,y1)格子开始到(x2,y2)格子结束(x1=x2或y1=y2),将所有满足x1<=x<=x2并且y1<=y<=y2的格子涂黑。奎奎想知道当他画完每一笔之后,这幅画的艺术价值是多少。
输入
第一行三个整数n,m,q (1≤n, m≤1000, 1≤q≤10000 )
下面q行每行4个整数x1,y1,x2,y2 (1≤x1≤x2≤n, 1≤y1≤y2≤m)描述奎奎画的q条线段的起点和终点
输出
q行,对于奎奎画的每一条线段,输出一行一个整数表示该线段画完之后画布上白色联通块的个数。
样例输入 Copy
4 6 5 2 2 2 6 1 3 4 3 2 5 3 5 4 6 4 6 1 6 4 6
样例输出 Copy
1 3 3 4 3
比赛的时候还剩二十分钟开始切这个题,读完题后的思维跳跃也是一波三折,队友先是提出二维差分维护(读错题了),被我一口回绝,因为题目要求强制在线,二维差分时间复杂度高达1e10,肯定不行,讨论了一小会无果,准备放弃,这时队友发现了关键点,题目给出的每次更新保证了是一个直线,(一开始我也读错题了,以为每次需要更新一个子矩阵。。),这个时候暴力维护的复杂度瞬间下降到了 1e7 ,感觉又变成了一道可以切的题,于是研究如何优化,虽然维护矩阵的复杂度到了1e7,但是维护答案的复杂度仍然是1e10,考虑强制在线的另一种做法就是离线处理,正难则反,不难想到倒着维护,在这个思路的引导向,逐渐想出了正解,也就是并查集维护
题目分析:其实想到并查集倒着维护就比较简单了,对于每条黑线,我们用maze[ i ][ j ]表示每个点被染成黑色的次数,在读入数据时,顺便将整个矩阵维护为添加了 q 条黑边后的状态,此时bfs或dfs找出所有白色的连通块,并用并查集维护(用搜索找连通块纯粹是因为好写),然后倒着遍历每一条黑色的边,将其还原为白色,对于一条直线上的某个点而言,如果这个点在删除掉当前黑线后,变为了白色,那么无非只有两种情况:
- 当前的点是新出现的一个白色连通块
- 当前的点和周围的白色连通块相连
分类讨论一下就好了,注意一下,如果当前点周围的白色连通块的数量等于一的话,那么当前点只是单纯的和这个白色连通块连接起来了,对答案没有影响,如果周围白色联通卡的数量大于一的话,那么就将其连到一起了,具体的看代码实现吧,这一点点逻辑把我绕了有小半个小时
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e3+100;
const int b[4][2]={0,1,0,-1,1,0,-1,0};
int maze[N][N],n,m,k,ans[10100],f[N*N],sum=0;
bool vis[N][N];
int get_id(int x,int y)
{
return (x-1)*m+y-1;
}
int find(int x)
{
return x==f[x]?x:f[x]=find(f[x]);
}
bool merge(int x,int y)
{
int xx=find(x);
int yy=find(y);
if(xx!=yy)
{
f[xx]=yy;
return true;
}
return false;
}
void dfs(int x,int y,int sx,int sy)
{
merge(get_id(x,y),get_id(sx,sy));
vis[x][y]=true;
for(int i=0;i<4;i++)
{
int xx=x+b[i][0];
int yy=y+b[i][1];
if(xx<=0||yy<=0||xx>n||yy>m)
continue;
if(vis[xx][yy]||maze[xx][yy])
continue;
dfs(xx,yy,sx,sy);
}
}
struct Que
{
int x1,x2,y1,y2;
void input()
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
for(int i=min(x1,x2);i!=max(x1,x2)+1;i++)
for(int j=min(y1,y2);j!=max(y1,y2)+1;j++)
maze[i][j]++;
}
void change()
{
for(int i=min(x1,x2);i!=max(x1,x2)+1;i++)
for(int j=min(y1,y2);j!=max(y1,y2)+1;j++)
maze[i][j]--;
}
void solve()
{
change();
int res=0;//记录当前直线沟通了多少个连通块
for(int i=min(x1,x2);i!=max(x1,x2)+1;i++)
for(int j=min(y1,y2);j!=max(y1,y2)+1;j++)
{
if(maze[i][j])//删去后仍然为黑色
continue;
int cnt=0;//记录周围有多少个不同的白色连通块
for(int k=0;k<4;k++)
{
int xx=i+b[k][0];
int yy=j+b[k][1];
if(xx<=0||yy<=0||xx>n||yy>m)
continue;
if(maze[xx][yy])
continue;
if(merge(get_id(i,j),get_id(xx,yy)))
cnt++;
}
if(cnt==0)//新出现的单独连通块
sum++;
else//否则记录沟通了多少个连通块
res+=cnt-1;
}
sum-=res;
}
}q[10100];
void init()
{
for(int i=0;i<=get_id(n,m);i++)
f[i]=i;
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
scanf("%d%d%d",&n,&m,&k);
init();
for(int i=1;i<=k;i++)
q[i].input();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(!vis[i][j]&&!maze[i][j])//找白色连通块
{
dfs(i,j,i,j);
sum++;
}
for(int i=k;i>=1;i--)//维护答案
{
ans[i]=sum;
q[i].solve();
}
for(int i=1;i<=k;i++)
printf("%d\n",ans[i]);
return 0;
}