BZOJ传送门
洛谷传送门
题目描述
佳佳有一个 n 行 n 列的黑白棋盘,每个格子都有两面,一面白色,一面黑色。佳佳把棋盘平放在桌子上,因此每个格子恰好一面朝上,如下图所示:
我们把每行从上到下编号为 1 , 2 , 3 ,……, n ,各列从左到右编号为 1 , 2 , 3 ,……, nn ,则每个格子可以用棋盘坐标 (x,y) ( x , y ) 表示。在上图中,有 8 个格子黑色朝上,另外 17 个格子白色朝上。
如果两个同色格子有一条公共边,我们称这两个同色格子属于同一个连通块。上图共有 5 个黑色连通块和 3 个白色连通块。
佳佳可以每分钟将一个格子翻转(即白色变成黑色,黑色变成白色),然后计算当前有多少个黑色连通块和白色连通块,你能算得更快吗?
输入输出格式
输入格式:
输入文件的第一行包含一个正整数 n ,为格子的边长。以下 n 行每行 n 个整数,非 0 即 1 ,表示初始状态。 0 表示白色, 1 表示黑色。下一行包含一个整数 m ,表示操作的数目。以下 m 行每行两个整数 x , y ( 1≤x,y≤n 1 ≤ x , y ≤ n ),表示把坐标为 (x,y) ( x , y ) 的格子翻转。
输出格式:
输出文件包含 m 行,每行对应一个操作。该行包括两个整数 b , w ,表示黑色区域和白色区域数目。
输入输出样例
输入样例#1:
5
0 1 0 0 0
0 1 1 1 0
1 0 0 0 1
0 0 1 0 0
1 0 0 0 0
2
3 2
2 3
输出样例#1:
4 3
5 2
样例说明
翻转 (3,2) ( 3 , 2 ) 之后,棋盘变为:
有 4 个黑色区域和 3个白色区域
翻转 (2,3) ( 2 , 3 ) 之后,棋盘变为:
有 5 个黑色区域和 2 个白色区域
【约定】
○ 1≤n≤200 1 ≤ n ≤ 200
○ 1≤m≤10,000 1 ≤ m ≤ 10 , 000
解题分析
如果还不会LCT维护图连通性的同学, 可以参考我的这篇博客:戳这里
这也是一道维护图连通性的题, 不过显然变得毒瘤了很多。我们可以将每个格子看做一个点, 与旁边格子有四条边相连。 处理的过程中显然会遇到环的情况, 怎么处理呢? 同样, 我们维护有关删除时间的最大生成树, 在加入边的时候判断是否为环。 如果成为环, 则与原路径中删除时间最早的边比较, 若新加入边的删除时间更晚则删除之前的那条边。
至于更新色块的个数, 我们考虑格子四个方向的连边:如果原来颜色相同则删边, 颜色不同则连边。 因为我们保证没有环且保证路径上点删除时间尽量大, 所以我们可以用LCT维护联通信息, 只需判断是否在一棵子树即可。
不过此题离线实在恶心(蒟蒻并不会强制在线的做法…听说是线段树合并?)… 想想我们需要离线什么? 首先, 我们要预处理一开始边的连通性及初始答案; 其次, 我们要离线所有的修改操作, 分为四个方向考虑; 然后我们还要处理每条边删除的时间, 并以此为权值在LCT中连边(注意还会有多次加入的情况)……细节可以参考蒟蒻的代码(不忍直视)
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <cstdlib>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ls tree[now].son[0]
#define rs tree[now].son[1]
#define dad tree[now].fat
#define MX 205
#define MQ 10005
#define UB 40005
template <class T>
IN void in(T &x)
{
x = 0; R char c = gc;
W (!isdigit(c)) c = gc;
W (isdigit(c))
x = (x << 1) + (x << 3) + c - 48, c = gc;
}
namespace Off_Line
{
const short dx[5] = {0, -1, 0, 0, 1};
const short dy[5] = {0, 0, -1, 1, 0};
int dot, col[MX][MX], ans[2], cpy[MX][MX], cnt, q, opx[MQ], opy[MQ], out[MX * MX << 3];
struct Event
{
int x, y, tim, dis, id, col;
//tim表示当前边进行操作的时间
//dis表示当前边删除的时间
//id表示边的编号,col表示格子当前的颜色
bool typ;
}eve[MX * MX << 3];
IN bool operator < (const Event &x, const Event &y)
{return x.tim == y.tim ? x.typ < y.typ : x.tim < y.tim;}
IN int get_edge_id(R int a, R int b, R int c, R int d)
{
if(a == c)//横向边
{
if(b > d) std::swap(b, d);
return (a - 1) * (dot - 1) + b;
}
else//纵向边, 编号都大于横向边
{
if(a > c) std::swap(a, c);
return (dot - 1) * dot + (a - 1) * dot + b;
}
}
IN int get_dot_id(const int &x, const int &y) {return (x - 1) * dot + y;}//点编号
}
namespace LCT
{
using namespace Off_Line;
struct Node
{
int son[2], fat, mn, val, pos;
bool rev, ex;
//ex 表示是否存在(即是否已经被割)
IN void clear()
{
this -> son[0] = this -> son[1] = this -> fat = this ->pos = this ->rev = this -> ex = 0;
this ->val = this -> mn = 0x7f7f7f7f;
}
}tree[MX * MX << 4];
int st[MX * MX << 3], top, lef[MX * MX << 3], rig[MX * MX << 3];
IN bool get(const int &now) {return tree[dad].son[1] == now;}
IN void pushrev(const int &now) {std::swap(ls, rs), tree[now].rev ^= 1;}
IN bool nroot(const int &now) {return tree[dad].son[1] == now || tree[dad].son[0] == now;}
IN void pushup(const int &now)
{
tree[now].mn = tree[now].val, tree[now].pos = now;
if(ls) if(tree[ls].mn < tree[now].mn) tree[now].mn = tree[ls].mn, tree[now].pos = tree[ls].pos;
if(rs) if(tree[rs].mn < tree[now].mn) tree[now].mn = tree[rs].mn, tree[now].pos = tree[rs].pos;
}
IN void pushdown(const int &now)
{
if(tree[now].rev)
{
if(ls) pushrev(ls);
if(rs) pushrev(rs);
tree[now].rev = false;
}
}
IN void rotate(const int &now)
{
R bool dir = get(now);
R int fa = dad, grand = tree[fa].fat;
tree[fa].son[dir] = tree[now].son[dir ^ 1];
tree[tree[now].son[dir ^ 1]].fat = fa;
if(nroot(fa)) tree[grand].son[get(fa)] = now;
tree[now].fat = grand;
tree[now].son[dir ^ 1] = fa;
tree[fa].fat = now;
pushup(fa);
}
IN void splay(int now)
{
top = 0; R int x = now, fa, grand;
st[++top] = x;
W (nroot(x)) x = tree[x].fat, st[++top] = x;
W (top) pushdown(st[top--]);
W (nroot(now))
{
fa = dad, grand = tree[fa].fat;
if(nroot(fa)) rotate(get(fa) == get(now) ? fa : now);
rotate(now);
}
pushup(now);
}
IN void access(int now)
{
for (R int y = 0; now; y = now, now = tree[now].fat)
splay(now), tree[now].son[1] = y, pushup(now);
}
IN void make_root(const int &now)
{
access(now), splay(now), pushrev(now);
}
IN int find_root(int now)
{
access(now), splay(now);
W (ls) pushdown(now), now = ls;
return now;
}
IN void split(const int &x, const int &y)
{make_root(x), access(y), splay(y);}
IN void link(const int &x, const int &y)
{
make_root(x);
if(find_root(y) != x)
tree[x].fat = y;
}
IN void cut(const int &x, const int &y)
{
split(x, y);
tree[x].fat = tree[y].son[0] = 0;
pushup(y);
}
//----------------------以上是正常的LCT模板-------------------------
IN void add(const int &now, const int &cl)
{
int x = eve[now].x, y = eve[now].y, dis = eve[now].dis, id = eve[now].id;
if(find_root(y) == find_root(x))
{
split(x, y);
if(tree[y].mn > dis) return;
int tar = tree[y].pos;
cut(tar, lef[tar]);
cut(tar, rig[tar]);
tree[tar].clear();
}
else ans[cl]--; int nx = id + UB;
tree[nx].val = dis;
tree[nx].ex = true;
tree[nx].pos = nx;
lef[nx] = x, rig[nx] = y;//记录两边的点是哪两个, 方便cut的时候O(1)找到
link(nx, x), link(nx, y);
pushup(nx);
}
IN void del(const int &now)
{
int x = eve[now].x, y = eve[now].y, id = eve[now].id;
cut(id + UB, x), cut(id + UB, y);
tree[id + UB].ex = false;
}
}
using namespace Off_Line;
using namespace LCT;
int main(void)
{
int a, b, nx, ny, nn;
R int i, j;
in(dot);
memset(out, -1, sizeof(out));
for (i = 1; i <= dot; ++i)
for (j = 1; j <= dot; ++j)
in(col[i][j]), cpy[i][j] = col[i][j], ans[col[i][j]]++;//cpy里面装的是复制的方格, 第一次离线操作用
for (i = 1; i <= dot; ++i)//这一步是离线所有点两两之间的连通性
for (j = 1; j <= dot; ++j)
{
a = get_dot_id(i, j), b = col[i][j];
if(j != dot && col[i][j + 1] == col[i][j])
eve[++cnt].x = a, eve[cnt].y = a + 1, eve[cnt].tim = 0,
eve[cnt].id = get_edge_id(i, j, i, j + 1), eve[cnt].typ = 1, eve[cnt].col = b;
if(i != dot && col[i + 1][j] == col[i][j])
eve[++cnt].x = a, eve[cnt].y = a + dot, eve[cnt].tim = 0,
eve[cnt].id = get_edge_id(i, j, i + 1, j), eve[cnt].typ = 1, eve[cnt].col = b;
}
in(q);
for (i = 1; i <= q; ++i)
{
in(opx[i]), in(opy[i]);
a = get_dot_id(opx[i], opy[i]);
for (j = 1; j <= 4; ++j)//四个方向 删边或连边操作
{
nx = opx[i] + dx[j], ny = opy[i] + dy[j], nn = get_dot_id(nx, ny);
if(nx <= 0 || nx > dot || ny <= 0 || ny > dot) continue;
if(cpy[opx[i]][opy[i]] == cpy[nx][ny])
eve[++cnt].x = a, eve[cnt].y = nn, eve[cnt].tim = i,
eve[cnt].id = get_edge_id(opx[i], opy[i], nx, ny), eve[cnt].typ = 0;
else
eve[++cnt].x = a, eve[cnt].y = nn, eve[cnt].tim = i,
eve[cnt].id = get_edge_id(opx[i], opy[i], nx, ny), eve[cnt].typ = 1;
}
cpy[opx[i]][opy[i]] ^= 1;//在复制的图上修改
}
std::sort(eve + 1, eve + 1 + cnt);
for (i = 1; i <= cnt; ++i) eve[i].dis = q + 1;
for (i = cnt; i >= 1; --i)
{
if(~out[eve[i].id]) eve[i].dis = out[eve[i].id];//从后往前处理边删除时间
out[eve[i].id] = eve[i].tim;
}
for (i = 1; i <= UB; ++i) tree[i].val = 0x7f7f7f7f;//将所有实际存在的点的删除时间赋为正无穷(反正不能删)
for (i = 1; i <= cnt && !eve[i].tim; ++i)
add(i, eve[i].col);//加边操作
for (j = 1; j <= q; ++j)
{
a = col[opx[j]][opy[j]];
for (; i <= cnt && eve[i].tim <= j; ++i)
if(!eve[i].typ)
{
if(!tree[eve[i].id + UB].ex) continue;
del(i);
++ans[a];
}
else add(i, a ^ 1);
--ans[a];//delete的时候会多计算一次, 所以要删掉
++ans[a ^ 1];//同上, 可以自己画图理解
col[opx[j]][opy[j]] ^= 1;
printf("%d %d\n", ans[1], ans[0]);
}
}