[BZOJ 1453] [Luogu P4121] [WC2005]双面棋盘

26 篇文章 0 订阅
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 ( 1x,yn 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 个白色区域

【约定】

1n200 1 ≤ n ≤ 200

1m10,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]);
    }

}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值