其他分享
首页 > 其他分享> > NFLSOJ #12458. 「NOIP2021模拟赛0929知临」棋盘(双栈模拟队列)

NFLSOJ #12458. 「NOIP2021模拟赛0929知临」棋盘(双栈模拟队列)

作者:互联网

题面传送门

之前不知道有”双栈模拟队列“这回事(主要是 9.23 模拟那场 T2 拿分治水过了就没管正解),写篇题解记录一下(

首先我们可以很自然地想到一个 DP,\(dp_{i,j}\) 表示有多少组到达第 \(i\) 行第 \(j\) 列所在格子的方案数,转移就从前两行刷表转移即可,复杂度 \(q^2n\),大约可以获得 \(30\) 分的好成绩(雾

考虑优化。注意到转移可以写成矩阵的方式,并且列数很小,只有 \(20\),因此可以想到矩阵乘法优化 DP。考虑离线建出最后的网格图,那么每次询问肯定是网格图上行在一段区间内的子矩形,经典的线段树维护矩阵乘法的模型,每次询问都求一遍区间矩阵乘积复杂度 \(qn^3\log q\),可以获得 \(70\) 分的好成绩。

继续优化。考虑这样一个事实:我们每次乘的矩阵中,\(1\) 的个数是 \(\mathcal O(n)\) 的,也就是说转移矩阵是一个稀疏矩阵,换句话说如果我们新加入一行,我们可以在 \(\mathcal O(n)\) 的时间内算出新的 DP 值,而不必 \(n^3\) 地跑一遍矩阵乘法。碰到这类插入复杂度很小,但合并复杂度很高的题可以想到将操作尽量转化为插入操作。怎么转化呢?一种常见的方法是分治,虽然应用到此题上不是不行,但对于此题而言使用分治复杂度会多一个 \(\log\),不知道能不能通过。

注意到我们每次都是在头添加元素,尾删除元素,也就是说添加/删除元素的过程组成了一个队列结构。因此考虑双栈模拟队列,具体来说我们实时维护一个端点 \(mid\),然后维护 \(L\to mid,mid+1\to R\) 的转移矩阵。考虑每次操作带来的影响:

显然每个点最多被重构一次,因此删除总复杂度均摊 \(qn^2\),而查询和加入复杂度都是单次 \(\mathcal O(n)\) 的,因此总复杂度 \(qn^2\)。

还有一点是由于这题马的移动轨迹涉及到前两行,因此 DP 状态还要记录上一行的状态,所以常数略有点大。

const int MAXQ=1e5;
const int MAXN=20;
int n,qu,mid,L=1,R=0;char s[MAXQ+5][MAXN+5];
struct dat{
	int a[MAXN+5][MAXN+5][2];
	dat(){memset(a,0,sizeof(a));}
	void clear(){memset(a,0,sizeof(a));}
};//0:current row 1:last row
dat rit[2],lft[2][MAXQ+5];
dat ins(dat x,int r){
	dat ret;
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) ret.a[i][j][1]=x.a[i][j][0];
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(s[r][j]!='#'){
		if(j-2>=1) ret.a[i][j][0]=(ret.a[i][j][0]+x.a[i][j-2][0])%MOD;
		if(j+2<=n) ret.a[i][j][0]=(ret.a[i][j][0]+x.a[i][j+2][0])%MOD;
		if(j-1>=1) ret.a[i][j][0]=(ret.a[i][j][0]+x.a[i][j-1][1])%MOD;
		if(j+1<=n) ret.a[i][j][0]=(ret.a[i][j][0]+x.a[i][j+1][1])%MOD;
	} return ret;
}
int main(){
	freopen("chess.in","r",stdin);
	freopen("chess.out","w",stdout);
	scanf("%d%d",&n,&qu);
	for(int _=1;_<=qu;_++){
		static char opt[10];scanf("%s",opt+1);
		if(opt[1]=='A'){
			R++;scanf("%s",s[R]+1);
			if(R==mid+1){
				rit[0].clear();rit[1].clear();
				for(int i=1;i<=n;i++) rit[0].a[i][i][0]=(s[R][i]=='.');
			} else if(R==mid+2){
				rit[0]=ins(rit[0],R);
				for(int i=1;i<=n;i++) rit[1].a[i][i][0]=(s[R][i]=='.');
			} else rit[0]=ins(rit[0],R),rit[1]=ins(rit[1],R);
//			for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) printf("%d%c",rit[0].a[i][j][0]," \n"[j==n]);
//			for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) printf("%d%c",rit[1].a[i][j][0]," \n"[j==n]);
		} else if(opt[1]=='D'){
			L++;
			if(L>mid){
				mid=R;rit[0].clear();rit[1].clear();
				for(int i=L;i<=R;i++) lft[0][i].clear(),lft[1][i].clear();
				for(int i=1;i<=n;i++) lft[0][mid].a[i][i][0]=(s[mid][i]=='.');
				for(int i=1;i<=n;i++) lft[1][mid-1].a[i][i][0]=(s[mid-1][i]=='.');
				for(int i=mid-1;i>=L;i--) lft[0][i]=ins(lft[0][i+1],i);
				for(int i=mid-2;i>=L;i--) lft[1][i]=ins(lft[1][i+1],i);
			}
		} else {
			int x,y;scanf("%d%d",&x,&y);
			if(L>R){puts("0");continue;}
			if(s[L][x]=='#'||s[R][y]=='#'){puts("0");continue;}
			if(L==R){printf("%d\n",(x==y));continue;}
			if(R==mid){printf("%d\n",lft[0][L].a[y][x][0]);continue;}
			if(L>mid){printf("%d\n",rit[0].a[x][y][0]);continue;}
			int res=0;
			for(int i=1;i<=n;i++){
				if(i-2>=1) res=(res+1ll*lft[0][L].a[i][x][0]*rit[0].a[i-2][y][0])%MOD;
				if(i+2<=n) res=(res+1ll*lft[0][L].a[i][x][0]*rit[0].a[i+2][y][0])%MOD;
				if(i-1>=1) res=(res+1ll*lft[1][L].a[i][x][0]*rit[0].a[i-1][y][0])%MOD;
				if(i+1<=n) res=(res+1ll*lft[1][L].a[i][x][0]*rit[0].a[i+1][y][0])%MOD;
				if(i-1>=1) res=(res+1ll*lft[0][L].a[i][x][0]*rit[1].a[i-1][y][0])%MOD;
				if(i+1<=n) res=(res+1ll*lft[0][L].a[i][x][0]*rit[1].a[i+1][y][0])%MOD;
			} printf("%d\n",res);
		}
	}
	return 0;
}
/*
5 100
Add .....
Add .....
Del
Del
Add .....
Add .....
Del
Del
Add .....
Add .....
Que 2 4
Add .....
Add .....
Que 2 4
Del
Que 2 3
Del
Que 2 4
*/

标签:0929,NFLSOJ,int,res,复杂度,矩阵,mid,lft,模拟
来源: https://www.cnblogs.com/ET2006/p/NFLSOJ-12458.html