https://www.luogu.com.cn/problem/P4997
并查集+模拟+分块+\(bitset\)
一种与众不同的解法
我\(AC\)后一看题解,发现大佬们都更改了气的定义,顿时觉得我太弱了\(QAQ\)
我做题时并没有更改气的定义,然后并查集的同时去计算气,发现气是会重复的,本来想特判,但是下面一种情况就挂了
观察一下,\(5\)个\(X\)的联通块有\(4\)气,\(2\)个\(X\)的联通块有\(5\)气
现在我们在\(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;
}