Luogu4997 不围棋

https://www.luogu.com.cn/problem/P4997

并查集+模拟+分块+\(bitset\)

一种与众不同的解法

\(AC\)后一看题解,发现大佬们都更改了的定义,顿时觉得我太弱了\(QAQ\)

我做题时并没有更改气的定义,然后并查集的同时去计算气,发现气是会重复的,本来想特判,但是下面一种情况就挂了

\[ \begin{vmatrix} X & X & X& X\\ . & . & . & X\\ . & . & X & .\\ . & . & X & .\\ \end{vmatrix} \]

观察一下,\(5\)\(X\)的联通块有\(4\)气,\(2\)\(X\)的联通块有\(5\)

\[ \begin{vmatrix} X & X & X& X\\ . & . & b & X\\ . & . & X & a\\ . & . & X & c\\ \end{vmatrix} \]

现在我们在\(a\)处放入\(X\),显然原本两个联通块连成\(1\)

两个联通块\(a\)处的气都没了,需要减去;\(a\)新产生的气\(c\)是原本两个\(X\)也拥有的,不应当重复计算

但是上面都可以处理,关键是\(b\)处,它是原本两个块公共的部分,不应当重复计算,但是我们无法提前预知两块会合并,所以根本无法计算

之所以无法计算,是因为这里取的是并集,而不是简单的加减法

并集,很容易想到状压,让后\(or\)处理,观察数据范围,\(n \le 600\),又一下子想到了\(bitset\)

但是棋子的联通块范围是很广的,我们需要维护全局的信息,这意味着我们对于每一个位置,都必须用一个\(600\times 600=360000\)的大小的\(bitset\)维护

这样是不是太浪费了?

我们考虑,棋子的联通块范围是很广的仅仅是少数的联通块,而对于左上角的一个单独的棋子,它根本不需要维护右下角的信息

这样我们就可以进行分块了,把整个棋盘拆成很多块,如果一个联通块在哪个块有,就开一个包含该块信息的空间

动态开空间,我们需要\(vector\),除了\(bitset\)之外,我们还需要记录这是哪个块

那么:\(vector< int,bitset >\)

那么我们就可以轻松使用\(or\)运算了

合并时,我们把块数少的联通块合并到块数多的联通块上面,然后直接删光被合并的联通块的\(vector\)

如何分块呢,因为联通块向四周拓展,显然用九宫格的方式分块较为优秀

实际上,这样做时间、空间开销都是可以平衡到可接受的复杂度上的

然后,按顺序插入棋子,插入棋子前\(check\)一下是否能够插入,每个棋子在每个位置最多\(check\)一次,可以用两个队列维护

然后就\(AC\)

是不是深得暴力之精髓

\(C++ Code:\)

#include
#include
#include
#include
#include
#include
#define pr pair< int,bitset<605> >
#define Pr pair
#define mp make_pair
#define N 605
using namespace std;
int n,ni,nj,t[N][N];
int lft[1005],vis[1005],hlk[N],f[N*N],bel[N][N],bel2[N][N],h[N*N][2],use[N][N];
char s[N];
int dic[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
queueq1,q2;
struct node
{
    vector< pr >p;//一个块的信息
    int opt,cnt=0;
}g[N][N];
#define IT vector< pr >::iterator
int getf(int x)//并查集
{
    return (f[x]==x)?x:(f[x]=getf(f[x]));
}
void Reset(int l,int r,int x,int y)//把(l,r)在(x,y)的气删掉
{
    int fx=getf(t[l][r]),lx=h[fx][0],rx=h[fx][1];
    for (IT it=g[lx][rx].p.begin();it!=g[lx][rx].p.end();++it)//暴力遍历每个块
        if (it->first==bel[x][y])
        {
            it->second.reset(bel2[x][y]);
            if (!it->second.count())//该块没有气了,直接删掉
                g[lx][rx].p.erase(it);
            break;
        }
}
void ins(int opt,int x,int y)
{
    g[x][y].opt=opt;
    for (int i=0;i<4;i++)
    {
        int nx=x+dic[i][0];
        int ny=y+dic[i][1];
        if (nx<1 || nx>n || ny<1 || ny>n)
            continue;
        if (!g[nx][ny].opt)//插入新的气
        {
            bool flag=false;
            int fx=getf(t[x][y]);
            int l=h[fx][0],r=h[fx][1];
            for (IT it=g[l][r].p.begin();it!=g[l][r].p.end();it++)
                if (it->first==bel[nx][ny])
                {
                    it->second.set(bel2[nx][ny]);
                    flag=true;
                    break;
                }
            if (!flag)//没有该气所在块,新建一个
            {
                bitset<605>w;
                w.reset();
                w.set(bel2[nx][ny]);
                g[l][r].p.push_back(mp(bel[nx][ny],w));
                g[l][r].cnt++;
            }
        } else
        if (g[nx][ny].opt==opt)//联通块合并
        {
            int fx=getf(t[x][y]);
            int fy=getf(t[nx][ny]);
            if (fx==fy)
                continue;
            int lx=h[fx][0],rx=h[fx][1],ly=h[fy][0],ry=h[fy][1];
            if (g[lx][rx].cntfirst]=o;
            for (IT it=g[ly][ry].p.begin();it!=g[ly][ry].p.end();++it)
                if (vis[it->first])
                    g[lx][rx].p[vis[it->first]-1].second|=it->second; else
                    {
                        g[lx][rx].p.push_back(mp(it->first,it->second));
                        g[lx][rx].cnt++;
                    } 
            for (IT it=g[lx][rx].p.begin();it!=g[lx][rx].p.end();++it)
                vis[it->first]=0;
            g[ly][ry].p.clear();//删掉被合并的联通块的vector
        }
    }
    for (int i=0;i<4;i++)
    {
        int nx=x+dic[i][0];
        int ny=y+dic[i][1];
        if (nx<1 || nx>n || ny<1 || ny>n || !g[nx][ny].opt)
            continue;
        Reset(nx,ny,x,y);//把周围的联通块在该点的气删掉
    }
    Reset(x,y,x,y);
}
int Pre_Reset(int l,int r)//计算该联通块的气
{
    if (use[l][r])
        return 1;
    int qi=0;
    int fx=getf(t[l][r]),lx=h[fx][0],rx=h[fx][1];
    for (IT it=g[lx][rx].p.begin();it!=g[lx][rx].p.end();++it)
        qi+=it->second.count();
    return qi;
}
bool check(int opt,int x,int y)
{
    bool ans=true;
    int qi=0,Z_qi=0;
    for (int i=0;i<4;i++)
    {
        int nx=x+dic[i][0];
        int ny=y+dic[i][1];
        if (nx<1 || nx>n || ny<1 || ny>n)
            continue;
        if (!g[nx][ny].opt)
        {
            Z_qi++;
            continue;
        }
        int e=Pre_Reset(nx,ny);
        if (g[nx][ny].opt==opt)
            qi+=e-1; else
            {
                if (e==1)//把对方的棋吃掉了
                    ans=false;
            }
    }
    if (!Z_qi && !qi)//自己没有气,且周围新合并的联通块也没气了,不能填
        ans=false;
    for (int i=0;i<4;i++)
    {
        int nx=x+dic[i][0];
        int ny=y+dic[i][1];
        if (nx<1 || nx>n || ny<1 || ny>n || !g[nx][ny].opt)
            continue;
        use[nx][ny]=0;
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    int u=sqrt(n);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            t[i][j]=(i-1)*n+j,f[t[i][j]]=t[i][j],h[t[i][j]][0]=i,h[t[i][j]][1]=j;
    for (int i=1;i<=n;i++)
        hlk[i]=(i+u-1)/u;
    int mx=0;
    for (int i=1;i<=n;i++)//预处理每个位置在哪个块内
        for (int j=1;j<=n;j++)
        {
            bel[i][j]=(hlk[i]-1)*hlk[n]+hlk[j];
            bel2[i][j]=++lft[bel[i][j]];
        }
    for (int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        for (int j=1;j<=n;j++)
        {
            if (s[j]=='.')
                continue;
            ins((s[j]=='X')?1:2,i,j);
        }
    }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            if (!g[i][j].opt)
            {
                if (check(1,i,j))
                    q1.push(mp(i,j));
                if (check(2,i,j))
                    q2.push(mp(i,j));
            }
    for (int st=1;;st=3-st)
        if (st==1)
        {
            bool flag=false;
            while (!q1.empty())
            {
                Pr k=q1.front();
                q1.pop();
                if (g[k.first][k.second].opt)//已经填了
                    continue;
                if (check(st,k.first,k.second))//可以填
                {
                    ins(st,k.first,k.second);
                    printf("%d %d\n",k.first,k.second);
                    flag=true;
                    break;
                }
            }
            if (!flag)//没能匹配,结束
                break;
        } else
        {
            bool flag=false;
            while (!q2.empty())
            {
                Pr k=q2.front();
                q2.pop();
                if (g[k.first][k.second].opt)
                    continue;
                if (check(st,k.first,k.second))
                {
                    ins(st,k.first,k.second);
                    printf("%d %d\n",k.first,k.second);
                    flag=true;
                    break;
                }
            }
            if (!flag)
                break;
        }   
    puts("-1 -1");
    return 0;
}

你可能感兴趣的:(Luogu4997 不围棋)