chess草稿(附代码!)
2022/8/12日过了,代码如下:(已删除调试语句,保留注释,为了使代码更容易看懂并没有卡常。卡完常的代码不是给人看的)
点击查看代码
/*
倒序操作+合并连通块+维护集合,支持合并、区间查询+线段树合并
*/
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e6+50;
void InOut(int op)
{
freopen("chess.in","r",stdin);
if(op)
freopen("chess.out","w",stdout);
}
int N,M,Q;
bool Can(int x,int y)
{
if(x<1||y<1||x>N||y>M)
return false;
return true;
}
int ALL;//N*M
int Edge[4][MAXN<<1];
int Ans[MAXN];
struct Node
{
int Color,Level,x,y,Id;
}Piece[MAXN];
bool CanEat(Node a,Node b)//a CanEat b
{
if(a.Color!=b.Color&&a.Level>=b.Level)
return true;
return false;
}
bool cmp1(Node a,Node b)
{
if(a.Level==b.Level)
return a.Id<b.Id;
return a.Level<b.Level;
}
bool cmp2(Node a,Node b)
{
return a.Id<b.Id;
}
void LSHLevel()
{
sort(Piece+1,Piece+Q+1,cmp1);
for(int i=1;i<=Q;i++)
{
Piece[i].Level=i;
}
sort(Piece+1,Piece+Q+1,cmp2);
}
int HaveNode[MAXN];
int GetId(int op,int x,int y)//0:从上到下一行一行 :左右连续 1:从左到右一列一列 ,上下连续
{
if(op==0)
return (x-1)*M+y;
else
return (y-1)*N+x;
}
int GetX(int Id)
{
return (Id-1)/M+1;
}
int GetY(int Id)
{
return (Id-1)%M+1;
}
struct SegmentTree
{
int Root[MAXN];
int ls[MAXN<<2],rs[MAXN<<2],Size[MAXN<<2];
int CntId;
void Init()
{
memset(Root,0,sizeof(Root));
for(int i=1;i<=CntId;i++)
{
ls[i]=rs[i]=Size[i]=0;
}
CntId=0;
}
int Merge(int u1,int u2,int l,int r)
{
if(u1&&u2)
{
if(u1==u2)
return u1;
}
if(!u1||!u2)
{
return u1+u2;
}
if(l==r)
{
Size[u1]=max(Size[u1],Size[u2]);
return u1;
}
int Mid=l+r>>1;
ls[u1]=Merge(ls[u1],ls[u2],l,Mid);
rs[u1]=Merge(rs[u1],rs[u2],Mid+1,r);
Size[u1]=Size[ls[u1]]+Size[rs[u1]];
return u1;
}
void Insert(int &u,int l,int r,int x)
{
if(u==0)
{
u=++CntId;
}
if(l==r)
{
Size[u]=1;
return;
}
int Mid=l+r>>1;
if(x<=Mid)
Insert(ls[u],l,Mid,x);
else
Insert(rs[u],Mid+1,r,x);
Size[u]=Size[ls[u]]+Size[rs[u]];
}
void Delete(int &u,int l,int r,int x)
{
if(u==0)
{
return;
}
if(l==r)
{
Size[u]=0;
return;
}
int Mid=l+r>>1;
if(x<=Mid)
Delete(ls[u],l,Mid,x);
else
Delete(rs[u],Mid+1,r,x);
Size[u]=Size[ls[u]]+Size[rs[u]];
}
int Query(int u,int l,int r,int x,int y)
{
if(!u)
return 0;
if(x<=l&&r<=y)
{
return Size[u];
}
int Mid=l+r>>1,Sum=0;
if(x<=Mid&&y>=l)
Sum+=Query(ls[u],l,Mid,x,y);
if(x<=r&&y>=Mid+1)
Sum+=Query(rs[u],Mid+1,r,x,y);
return Sum;
}
bool Query(int u,int l,int r,int x)
{
if(u==0)
{
return false;
}
if(l==r)
{
if(Size[u])
return true;
else
return false;
}
int Mid=l+r>>1;
if(x<=Mid)
return Query(ls[u],l,Mid,x);
else
return Query(rs[u],Mid+1,r,x);
}
}trId[2],trLevel[2];//Id:0横1竖,Level[x]代表Color=x
void Insert1(int x,int y,int z)//(x,y)这个空点插入编号为z的连通块
{
trId[0].Insert(trId[0].Root[z],1,ALL,GetId(0,x,y));
trId[1].Insert(trId[1].Root[z],1,ALL,GetId(1,x,y));
}
void Insert2(int Nodeid,int z)//第Nodeid颗棋子插入编号为z的连通块
{
trLevel[Piece[Nodeid].Color].Insert(trLevel[0].Root[z],1,ALL,Piece[Nodeid].Level);
}
struct BingChaJi
{
int father[MAXN],Min[MAXN],Max[MAXN];//father都要用到,Min和Max只在维护横竖线的时候用到
void Init()
{
for(int i=1;i<=ALL;i++)
{
father[i]=i;
Min[i]=1e9;Max[i]=0;
}
}
int getfather(int x)
{
if(x!=father[x])
father[x]=getfather(father[x]);
return father[x];
}
void Merge(int x,int y)//x是father,y是son
{
int fx=getfather(x),fy=getfather(y);
if(fx==fy)
return;
father[fy]=fx;
}
}Set[3];//Set[0]:连通块,Set[1]:横,Set[2]:竖
void Init()
{
memset(Edge,0,sizeof(Edge));
memset(Ans,0,sizeof(Ans));
memset(HaveNode,0,sizeof(HaveNode));
trId[0].Init();
trId[1].Init();
trLevel[0].Init();
trLevel[1].Init();
Set[0].Init();
Set[1].Init();
Set[2].Init();
}
char opt[MAXN];
int dx[5]={0,-1,1,0,0},dy[5]={0,0,0,-1,1};//对应Edge,上下左右
//方向op要比Edgeop大1,但相对位置不变
//竖着的GetId要想好是0还是1,因为Edge永远都是0
bool vis[MAXN];
int Mergedfs(int op,int x,int y,int fa)
{
if(op==4)
{
vis[GetId(0,x,y)]=true;
if(HaveNode[GetId(0,x,y+1)]==0&&Can(x,y+1)&&Edge[3][GetId(0,x,y)]==2)
{
Set[1].Merge(Set[1].getfather(GetId(0,x,fa)),Set[1].getfather(GetId(0,x,y+1)));
return Mergedfs(op,x,y+1,fa);
}
else
{
return y;
}
}
if(op==2)
{
vis[GetId(0,x,y)]=true;
if(HaveNode[GetId(0,x+1,y)]==0&&Can(x+1,y)&&Edge[1][GetId(0,x,y)]==2)
{
Set[2].Merge(Set[2].getfather(GetId(0,fa,y)),Set[2].getfather(GetId(0,x+1,y)));
return Mergedfs(op,x+1,y,fa);
}
else
{
return x;
}
}
}
//Id线段树是1-ALL,Level线段树是1-Q
void dfs3(int x,int y,int fx,int fy)
{
vis[GetId(0,x,y)]=true;
trId[0].Insert(trId[0].Root[GetId(0,fx,fy)],1,ALL,GetId(0,x,y));
trId[1].Insert(trId[1].Root[GetId(0,fx,fy)],1,ALL,GetId(1,x,y));//线段树只是记录竖编号,Root还是统一横编号!
for(int i=0;i<4;i++)
{
if(Edge[i][GetId(0,x,y)]==3&&vis[GetId(0,x+dx[i+1],y+dy[i+1])]==false)
{
if(HaveNode[GetId(0,x+dx[i+1],y+dy[i+1])]==0)
{
Set[0].Merge(Set[0].getfather(GetId(0,fx,fy)),Set[0].getfather(GetId(0,x+dx[i+1],y+dy[i+1])));
dfs3(x+dx[i+1],y+dy[i+1],fx,fy);
}
else
{
int t=HaveNode[GetId(0,x+dx[i+1],y+dy[i+1])];
trLevel[Piece[t].Color].Insert(trLevel[Piece[t].Color].Root[GetId(0,fx,fy)],1,Q,Piece[t].Level);
}
}
}
}
void Read()
{
scanf("%d%d%d",&N,&M,&Q);
ALL=N*M;
Init();
for(int i=1;i<=N;i++)
{
scanf("%s",&opt[1]);
for(int j=1;j<M;j++)
{
//opt[j]表示(i,j)至(i,j+1)的边
Edge[3][GetId(0,i,j)]=Edge[2][GetId(0,i,j+1)]=opt[j]-'0';
}
}
for(int i=1;i<N;i++)
{
scanf("%s",&opt[1]);
for(int j=1;j<=M;j++)
{
Edge[0][GetId(0,i+1,j)]=Edge[1][GetId(0,i,j)]=opt[j]-'0';
}
}
for(int i=1;i<=Q;i++)
{
scanf("%d%d%d%d",&Piece[i].Color,&Piece[i].Level,&Piece[i].x,&Piece[i].y);
Piece[i].Id=i;
HaveNode[GetId(0,Piece[i].x,Piece[i].y)]=i;
}
}
//除了trId[1]要用GetId(1)外其它都用GetId(0)
int SolveEdge1(Node Now)
{
int ans=0;
int fx=Set[0].getfather(GetId(0,Now.x,Now.y));
for(int i=0;i<4;i++)
{
if(Edge[i][GetId(0,Now.x,Now.y)]==1)
{
if(HaveNode[GetId(0,Now.x+dx[i+1],Now.y+dy[i+1])]==0)//旁边空格
{
if(trId[0].Query(trId[0].Root[fx],1,ALL,GetId(0,Now.x+dx[i+1],Now.y+dy[i+1]))==false)//空格,走3走不到
{
ans++;
}
}
else
{
if(CanEat(Now,Piece[HaveNode[GetId(0,Now.x+dx[i+1],Now.y+dy[i+1])]]))
{
int NowColor=Piece[HaveNode[GetId(0,Now.x+dx[i+1],Now.y+dy[i+1])]].Color,NowLevel=Piece[HaveNode[GetId(0,Now.x+dx[i+1],Now.y+dy[i+1])]].Level;
if(trLevel[NowColor].Query(trLevel[NowColor].Root[fx],1,Q,NowLevel)==false)//吃子,走3吃不到
{
ans++;
}
}
}
}
}
return ans;
}
vector<int>Eatid;
int SolveEdge2(Node Now)
{
int ans=0;
int l1,r1,l2,r2;//l1-r1横,l2-r2竖
l1=Set[1].Min[Set[1].getfather(GetId(0,Now.x,Now.y))];
r1=Set[1].Max[Set[1].getfather(GetId(0,Now.x,Now.y))];
l2=Set[2].Min[Set[2].getfather(GetId(0,Now.x,Now.y))];
r2=Set[2].Max[Set[2].getfather(GetId(0,Now.x,Now.y))];
int fx=Set[0].getfather(GetId(0,Now.x,Now.y));
//空格:能不能被3走到
ans+=r1-l1+1-trId[0].Query(trId[0].Root[fx],1,ALL,GetId(0,Now.x,l1),GetId(0,Now.x,r1));//因为先Delete,所以trId包含了自身,而r1-l1+1刚好包含了自身,所以消掉了
ans+=r2-l2+1-trId[1].Query(trId[1].Root[fx],1,ALL,GetId(1,l2,Now.y),GetId(1,r2,Now.y));
//吃子:最多4颗,能不能被3吃到
Eatid.clear();
if(Edge[2][GetId(0,Now.x,l1)]==2)//能往左边走(不是边界,却不是l1,则说明是有棋子)
Eatid.push_back(GetId(0,Now.x,l1-1));
if(Edge[3][GetId(0,Now.x,r1)]==2)
Eatid.push_back(GetId(0,Now.x,r1+1));
if(Edge[0][GetId(0,l2,Now.y)]==2)
Eatid.push_back(GetId(0,l2-1,Now.y));
if(Edge[1][GetId(0,r2,Now.y)]==2)
Eatid.push_back(GetId(0,r2+1,Now.y));
for(int i=0;i<Eatid.size();i++)
{
if(CanEat(Now,Piece[HaveNode[Eatid[i]]]))
{
int NowColor=Piece[HaveNode[Eatid[i]]].Color,NowLevel=Piece[HaveNode[Eatid[i]]].Level;
if(trLevel[NowColor].Query(trLevel[NowColor].Root[fx],1,Q,NowLevel)==false)
{
ans++;
}
}
}
return ans;
}
int SolveEdge3(Node Now)
{
int ans=0;
int fx=Set[0].getfather(GetId(0,Now.x,Now.y));
ans+=trId[0].Size[trId[0].Root[fx]]-trId[0].Query(trId[0].Root[fx],1,ALL,GetId(0,Now.x,Now.y));
ans+=trLevel[Now.Color^1].Query(trLevel[Now.Color^1].Root[fx],1,Q,1,Now.Level-1);
return ans;
}
vector<int>E2,E3;
void Del(Node Now)
{
HaveNode[GetId(0,Now.x,Now.y)]=0;
E3.clear();
for(int i=0;i<4;i++)
{
if(Edge[i][GetId(0,Now.x,Now.y)]==3)
{
E3.push_back(GetId(0,Now.x+dx[i+1],Now.y+dy[i+1]));
}
}
int ffx=Set[0].getfather(GetId(0,Now.x,Now.y));
trId[0].Insert(trId[0].Root[ffx],1,ALL,GetId(0,Now.x,Now.y));
trId[1].Insert(trId[1].Root[ffx],1,ALL,GetId(1,Now.x,Now.y));
for(int i=0;i<E3.size();i++)
{
int fx=Set[0].getfather(GetId(0,Now.x,Now.y)),fy=Set[0].getfather(E3[i]);
if(HaveNode[E3[i]])
{
int NowColor=Piece[HaveNode[E3[i]]].Color,NowLevel=Piece[HaveNode[E3[i]]].Level;
trLevel[NowColor].Insert(trLevel[NowColor].Root[fx],1,Q,NowLevel);
continue;
}
if(trId[0].Size[trId[0].Root[fy]])
trId[0].Root[fx]=trId[0].Merge(trId[0].Root[fx],trId[0].Root[fy],1,ALL);
if(trId[1].Size[trId[1].Root[fy]])
trId[1].Root[fx]=trId[1].Merge(trId[1].Root[fx],trId[1].Root[fy],1,ALL);
if(trLevel[0].Size[trLevel[0].Root[fy]])
trLevel[0].Root[fx]=trLevel[0].Merge(trLevel[0].Root[fx],trLevel[0].Root[fy],1,Q);
if(trLevel[1].Size[trLevel[1].Root[fy]])
trLevel[1].Root[fx]=trLevel[1].Merge(trLevel[1].Root[fx],trLevel[1].Root[fy],1,Q);
Set[0].Merge(fx,fy);
}
trLevel[Now.Color].Delete(trLevel[Now.Color].Root[ffx],1,Q,Now.Level);
//从可以被吃的点变成可以被走过的空格
if(Edge[0][GetId(0,Now.x,Now.y)]==Edge[1][GetId(0,Now.x,Now.y)]&&Edge[0][GetId(0,Now.x,Now.y)]==2&&HaveNode[GetId(0,Now.x-1,Now.y)]==0&&HaveNode[GetId(0,Now.x+1,Now.y)]==0)
{
int fx=Set[2].getfather(GetId(0,Now.x-1,Now.y)),fy=Set[2].getfather(GetId(0,Now.x+1,Now.y));
Set[2].Max[fx]=max(Set[2].Max[fx],Set[2].Max[fy]);//可以直接赋值,这样保险
Set[2].Min[fx]=min(Set[2].Min[fx],Set[2].Min[fy]);//肯定转移不了
Set[2].Merge(fx,fy);
Set[2].Merge(Set[2].getfather(GetId(0,Now.x-1,Now.y)),Set[2].getfather(GetId(0,Now.x,Now.y)));
}
else
{
if(Edge[0][GetId(0,Now.x,Now.y)]==2&&HaveNode[GetId(0,Now.x-1,Now.y)]==0)
{
Set[2].Merge(Set[2].getfather(GetId(0,Now.x-1,Now.y)),Set[2].getfather(GetId(0,Now.x,Now.y)));
Set[2].Max[Set[2].getfather(GetId(0,Now.x-1,Now.y))]=Now.x;
}
else
{
if(Edge[1][GetId(0,Now.x,Now.y)]==2&&HaveNode[GetId(0,Now.x+1,Now.y)]==0)
{
Set[2].Merge(Set[2].getfather(GetId(0,Now.x+1,Now.y)),Set[2].getfather(GetId(0,Now.x,Now.y)));
Set[2].Min[Set[2].getfather(GetId(0,Now.x+1,Now.y))]=Now.x;
}
else
{
Set[2].Min[GetId(0,Now.x,Now.y)]=Set[2].Max[GetId(0,Now.x,Now.y)]=Now.x;
}
}
}
if(Edge[2][GetId(0,Now.x,Now.y)]==Edge[3][GetId(0,Now.x,Now.y)]&&Edge[2][GetId(0,Now.x,Now.y)]==2&&HaveNode[GetId(0,Now.x,Now.y-1)]==0&&HaveNode[GetId(0,Now.x,Now.y+1)]==0)
{
int fx=Set[1].getfather(GetId(0,Now.x,Now.y-1)),fy=Set[1].getfather(GetId(0,Now.x,Now.y+1));
Set[1].Max[fx]=max(Set[1].Max[fx],Set[1].Max[fy]);
Set[1].Min[fx]=min(Set[1].Min[fx],Set[1].Min[fy]);
Set[1].Merge(fx,fy);
Set[1].Merge(Set[1].getfather(GetId(0,Now.x,Now.y-1)),Set[1].getfather(GetId(0,Now.x,Now.y)));
}
else
{
if(Edge[2][GetId(0,Now.x,Now.y)]==2&&HaveNode[GetId(0,Now.x,Now.y-1)]==0)
{
Set[1].Merge(Set[1].getfather(GetId(0,Now.x,Now.y-1)),Set[1].getfather(GetId(0,Now.x,Now.y)));
Set[1].Max[Set[1].getfather(GetId(0,Now.x,Now.y-1))]=Now.y;
}
else
{
if(Edge[3][GetId(0,Now.x,Now.y)]==2&&HaveNode[GetId(0,Now.x,Now.y+1)]==0)
{
Set[1].Merge(Set[1].getfather(GetId(0,Now.x,Now.y+1)),Set[1].getfather(GetId(0,Now.x,Now.y)));
Set[1].Min[Set[1].getfather(GetId(0,Now.x,Now.y+1))]=Now.y;
}
else
{
Set[1].Min[GetId(0,Now.x,Now.y)]=Set[1].Max[GetId(0,Now.x,Now.y)]=Now.y;
}
}
}
}
void Solve()
{
Read();
LSHLevel();
//预处理:
//2类边:
for(int i=1;i<=ALL;i++)
vis[i]=false;
//横:
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
{
if(HaveNode[GetId(0,i,j)]==0&&vis[GetId(0,i,j)]==0)
{
Set[1].Min[GetId(0,i,j)]=j;
Set[1].Max[GetId(0,i,j)]=Mergedfs(4,i,j,j);
}
}
}
//竖:
for(int i=1;i<=ALL;i++)
vis[i]=false;
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
{
if(HaveNode[GetId(0,i,j)]==0&&vis[GetId(0,i,j)]==0)
{
Set[2].Min[GetId(0,i,j)]=i;
Set[2].Max[GetId(0,i,j)]=Mergedfs(2,i,j,i);
}
}
}
//3类边:
for(int i=1;i<=ALL;i++)
vis[i]=false;
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
{
if(vis[GetId(0,i,j)]==0&&HaveNode[GetId(0,i,j)]==0)
{
dfs3(i,j,i,j);
}
}
}
//开始模拟
for(int i=Q;i>=1;i--)
{
Del(Piece[i]);
Ans[i]=SolveEdge1(Piece[i])+
SolveEdge2(Piece[i])+
SolveEdge3(Piece[i]);
}
for(int i=1;i<=Q;i++)
{
printf("%d\n",Ans[i]);
}
}
int main()
{
InOut(1);
int T;
scanf("%d",&T);
while(T--)
{
Solve();
}
}
草稿,而不是题解
chess:大体思路
棋盘行数:N
棋盘列数:M
边的类型:(0类)不能走,(1类)走一步,(2类)一直朝同方向走,(3类)随便走
棋子 颜色:0,1
棋子 个数:Q
棋子等级:[1,Q]
走子规则:只能走同一条边
吃子规则: 颜色不同,等级小于等于的棋子可以吃
求:放上一个棋子,能走到多少个位置
不吃子时:
倒着来看,先算当前棋子的答案:
1:3类边答案
2:去重
1类边:枚举这条边能到的4个点的id,线段树查一下有没有,没有则ans++
2类边:维护当前能到的最远点,左右横着编号,上下竖着编号(下一步就是边界或棋子,不包含下一步)
3类边:维护连通块,每次算完一个棋子都要合并-->如何合并?
去重:3类边开2个动态开点线段树,一个代表横着编号,一个代表竖着编号
- 枚举周围1类边不在编号线段树里的点有多少个
- 枚举2对方向,对于横着的方向,能走的有最左...左一格,右一格...最右,=r-l 然后在线段树横着那一棵查询[l,r]区间内的和,减去
- 加上2线段树的根节点和
注意两棵线段树均不包含当前讨论的点,题目要求不含当前点。
更新图的状态:
开 2 棵线段树,表示当前连通块能走到的白点的Level,黑点的Level(权值线段树)
把当前点周围能到达的不同的联通块对应的(4棵)线段树分别合并起来
并查集维护连通块编号。
2类边:并查集,维护最左最右、最上最下。删掉当前点后最多合并2次
综上:
4棵线段树:横着编号连通块,竖着编号连通块,连通块白点Level,连通块黑点Level(找的时候找严格小于当前讨论点的Level 的)
3个并查集:连通块,最左最右编号,最上最下编号
规定细节:最左最右最上最下编号不包含有棋子的点,即不能吃。吃棋子单独讨论,跟颜色线段树进行去重
编号线段树不包含可以吃的子
颜色线段树只能包含可以吃的子
删掉当前子之后合并出来的编号线段树要包含当前点,颜色线段树要去掉当前点
预处理:
把所有棋子放上去,预处理出:
找到极长横(竖)线,此段的father都是左边那个,然后Min都是左边,Max都是右边
合并的时候Min为左边(上面)的Min,Max为右边(下面)的Max
father[右(上)根]=左(下)根
找到每一个连通块,合并
对于每一个放棋子的点,看看周围的连通块,周围的连通块的线段树更新此点
本文来自博客园,作者:0htoAi,转载请注明原文链接:https://www.cnblogs.com/0htoAi/p/16577716.html