CSP-J复赛模拟赛4 王晨旭补题 2026.10.5

一,前言

哈喽哈喽又来写补题报告了,今天还挺开心啊,简单写写补题吧

今天的第三,四题依旧官方验证的难,打成这样还挺不错的了嘿嘿

二,成绩

1,四维矩形【100/100】幽默第一题

2,合体【100/100】前年最水第二题,今年最幽默第二题

3,同步机器人【70/100】心态决定成败,显然这次成了

4,矩阵【25/100】最逆天第四题太不体面了(其实是第三题吧宝子)

总分:【295/400】有300的可能吗,今日班级rank?

依旧两个重复题目哦,感兴趣的宝子建议回到24年第4次模拟赛补题

补充

本次第一第二题都是大水题在这里不说啦,本次重点放在第三,第四题,第一题语法题,对第二题感兴趣的宝子可以移步24年,一定注意大输入输出,切换scanf/printf!!!

三,题三

赛前:新第三题?是什么把矩阵赶到第四题了啊,抓紧瞅一眼,我去字符数组输入!我去不存图!我去久违的网格走路题!我去搜索模板!等着宝贝,我秒完俩水题就来找你

赛中:

p1:这啥东西,完蛋,深搜和宽搜到底用哪个啊,我靠应该是宽搜吧

p2:宽搜根本就写不下去啊,越写越崩,毁灭吧(嘻嘻其实心态还行),反正还有时间,直接换深搜,这把肯定行

p3:这里是地狱吗。。。

赛后:

p1:这一坨屎能拿七十分?爽了今天撞大运

p2:呜呜呜呜呜我的宽搜啊啊啊啊,改两行就100了为什么啊啊啊啊

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n,m,sx1,sy1,sx2,sy2,zx1,zy1,zx2,zy2,tx1,ty1,tx2,ty2,vis1[35][35],vis2[35][35],ans=1e9+7;
char a[35][35];
int dx[]={0,1,0,-1};
int dy[]={1,0,-1,0};
/*queue<pair<int,int> >q1,q2;*/
int check(int x,int y){
	if(a[x][y]=='#'||x>n||x<1||y>m||y<1){
		return 0;
	}
	return 1;
}
void dfs(int x1,int y1,int x2,int y2,int step){
	if(x1==zx1&&y1==zy1&&x2==zx2&&y2==zy2){
		ans=min(step,ans);
		return ;
	}
	for(int i=0;i<4;i++){
		int xx1=x1+dx[i],yy1=y1+dy[i],xx2=x2+dx[i],yy2=y2+dy[i];
		if(vis[xx1][yy1][xx2][yy2])	continue;
		if(!check(xx1,yy1)&&!check(xx2,yy2)){ 
			continue;
		}	
		if(!check(xx1,yy1)){
			xx1=x1;
			yy1=y1;
		}
		else if(!check(xx2,yy2)){
			xx2=x2;
			yy2=y2;
		}
		vis[xx1][yy1][xx2][yy2]=1;
		dfs(xx1,yy1,xx2,yy2,step+1);
		vis[xx1][yy1][xx2][yy2]=1; 
	}
}
/*void bfs(){写炸了的宽搜 
	int ans=0,flag=0;
	q1.push({sx1,sy1});//第一个机器人位置 
	q2.push({sx2,sy2});
	while(!q1.empty()){
		tx1=q1.front().first,ty1=q2.front().second;
		tx2=q2.front().first,tx2=q2.front().second;
		q1.pop();
		q2.pop();
		for(int i=0;i<4;i++){
			int xx1=tx1+dx[i],yy1=ty1+dy[i],xx2=tx2+dx[i],yy2=ty2+dy[i];
			if(vis[xx1][yy1]&&vis[xx2][yy2])	continue;//任意一方重复走一定不是答案 
			if(!check(xx1,yy1)){
				xx1=tx1;//越界不变
				yy1=ty1;
			}
			if(!check(xx2,yy2)){
				xx2=tx2;
				yy2=ty2;
			}
			q1.push({xx1,yy1});
			q2.push({xx2,yy2});
			vis[xx1][yy1]=1;
			step[xx1][yy1]=step[tx1][ty1]+1;
			if(xx1==zx1&&yy1==zy1&&xx2==zx2&&yy2==zy2){
				flag=1;
				ans=min(ans,step[xx1][yy1]);
			}
		}
	}
	//队列为空,没有可行路线
	if(flag==0){//未到达终点 
		cout<<-1;
	} 
	else{
		cout<<ans;
	}
}*/
int main(){
	//freopen("sync.in","r",stdin);
	//freopen("sync.out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	} 
	cin>>sx1>>sy1>>sx2>>sy2>>zx1>>zy1>>zx2>>zy2;
	dfs(sx1,sy1,sx2,sy2,0);
	if(ans==1e9+7){
		cout<<-1;
	}
	else{
		cout<<ans;
	}
}
 

(好吧这dfs完全不对)

好了这边是心路历程,回到学术方面

这个题我写了基本完整地两套代码

这个题的应用肯定是bfs(虽然最后写炸了)题面如下:

小 C 正在测试两个机器人。测试场地是一张 n 行 m 列的网格地图,其中字符 . 表示可以通行的空地,字符 # 表示不能通行的障碍物。

两个机器人分别称为机器人 A 和机器人 B。小 C 每次可以选择上、下、左、右中的一个方向,并向两个机器人同时发送这条移动指令。

收到指令后,两个机器人分别按照以下规则行动:

如果机器人沿指令方向移动一格后仍在地图内,并且到达的格子不是障碍物,那么它会移动到该格子。

否则,它会停留在原来的格子。

两个机器人的行动互不影响。它们可以同时位于同一个格子,也可以同时经过同一个格子。

机器人 A 和机器人 B 各有一个目标位置。小 C 希望在某次指令执行完毕后,两个机器人同时位于各自的目标位置。请你求出最少需要发送多少条指令。

机器人到达目标位置后不会自动停机。如果之后收到的指令能让它移动,它仍然会离开目标位置。

这么看来我的基础真的很差了,这样的最短路显然要使用bfs,若使用dfs去遍历所有路线将会TLE45分(不知道考场怎么拿到70)

写炸的根源是什么呢,把这两个机器人的状态分开了,这里的状态,不是移动时变化的状态,而是整体全局的状态,不管是写的宽搜还是广搜,都没有认清一个点:当前状态变化是由两个机器人共同决定的,单独 A 的坐标、单独 B 的坐标都不能代表完整局面,合在一起,才能代表一个独立状态,vis数组标记的状态不是一个位置,而是一个"局面"

我们重新看一下bfs队列的意义:里面存储着还没处理完的局面,和处理完一个局面后拓展出的局面,而存储的一定是局面,我的两个机器人状态分开存,两个独立队列无法保证,目前拿出的一组A、B 位置属于同一个局面,队列只能保证入队顺序对齐,分支一多,取出时A和B会跨局面胡乱配对,拿到的是不属于同一时刻的A、B组合,而结合成一个队列才能避免这个问题

这里补充两个我遇到的大坑

第一,BFS 第一次碰到终点状态,一定是最短步数

因为它分层遍历,距离(直接的最近距离)初始点相同的点将会在同一层遍历,不遍历完这一层的点,下一层甚至更远的点一定不会被遍历到,自然不会出现绕远路的情况,这一点与dfs求出所有路线步数求最小值不同

第二,vis\[\]\[\]数组看似标记的是格子和位置,其实完全不是,而是局面,是我这个主体是否曾经处在过这个局面,这点在单人迷宫还好,放到这个题混淆了是相当致命的,vis的作用是为了防止重复处理同一个局面的!这里所说的局面概念,是所有能影响后续走向的全部信息

而我在写代码的时候把【局面】拆成了两个独立个体,用两个独立队列分别保存 A、B, 潜意识认为:A 是一套独立搜索、B 是另一套独立搜索,之后再配对(出队时候的状态无从匹配)。但搜索的基本单元根本不是 A,也不是 B,而是【A+B 共同构成的局面】

按照这样的思路,我的考场代码底子还可以,开上四维vis,内容不再是简单标记0/1改为存储步数,队列合并成一个,注意越界判断在前,其实别的离正解真的很近了,卡的点就是没有理解局面的含义,注意要判断起点是否与终点重合(谁出的破样例让我掉了15分)

AC代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n,m,sx1,sy1,sx2,sy2,zx1,zy1,zx2,zy2,tx1,ty1,tx2,ty2,vis[35][35][35][35],ans=1e9+7;
char a[35][35];
int dx[]={0,1,0,-1};
int dy[]={1,0,-1,0};
struct node{
	int x1,y1,x2,y2;
};
queue<node>q;
int check(int x,int y){
	if(x>n||x<1||y>m||y<1){
		return 0;
	}
	if(a[x][y]=='#'){
		return 0;
	}
	return 1;
}
/*void dfs(int x1,int y1,int x2,int y2,int step){
	if(x1==zx1&&y1==zy1&&x2==zx2&&y2==zy2){
		ans=min(step,ans);
		return ;
	}
	for(int i=0;i<4;i++){
		int xx1=x1+dx[i],yy1=y1+dy[i],xx2=x2+dx[i],yy2=y2+dy[i];
		if(vis[xx1][yy1][xx2][yy2])	continue;
		if(!check(xx1,yy1)&&!check(xx2,yy2)){ 
			continue;
		}	
		if(!check(xx1,yy1)){
			xx1=x1;
			yy1=y1;
		}
		else if(!check(xx2,yy2)){
			xx2=x2;
			yy2=y2;
		}
		vis[xx1][yy1][xx2][yy2]=1;
		dfs(xx1,yy1,xx2,yy2,step+1);
		vis[xx1][yy1][xx2][yy2]=0; 
	}
}*/
void bfs(){
	int ans=0,flag=0;
	node w;
	w.x1=sx1,w.x2=sx2,w.y1=sy1,w.y2=sy2;
	vis[sx1][sy1][sx2][sy2]=0;
	q.push(w);
	while(!q.empty()){
		tx1=q.front().x1,ty1=q.front().y1;
		tx2=q.front().x2,ty2=q.front().y2;
		q.pop();
		for(int i=0;i<4;i++){
			int xx1=tx1+dx[i],yy1=ty1+dy[i],xx2=tx2+dx[i],yy2=ty2+dy[i];
			if(!check(xx1,yy1)){
				xx1=tx1;//越界不变
				yy1=ty1;
			}
			if(!check(xx2,yy2)){
				xx2=tx2;
				yy2=ty2;
			}
			if(vis[xx1][yy1][xx2][yy2]!=-1)	continue;//这个局面经历过了
			node ww;
			ww.x1=xx1;ww.x2=xx2;ww.y1=yy1;ww.y2=yy2;
			q.push(ww);//差点忘记了
			vis[xx1][yy1][xx2][yy2]=vis[tx1][ty1][tx2][ty2]+1;
			if(xx1==zx1&&yy1==zy1&&xx2==zx2&&yy2==zy2){
				cout<<vis[xx1][yy1][xx2][yy2];
				return ;
			}
		}
	}
	cout<<-1;
}
int main(){
	//freopen("sync.in","r",stdin);
	//freopen("sync.out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	} 
	cin>>sx1>>sy1>>sx2>>sy2>>zx1>>zy1>>zx2>>zy2;
	memset(vis,-1,sizeof vis);
	bfs();
}
 

四,题四

(呃熟人见面挺尴尬啊,这样我赶时间打个暴力给您,记得多给我点分)

神tm6层循环25分(n<=10),也是非常合理

本次判题机非常强大,剩下的75分n<=300的情况下竟然二维异或前缀和O()能直接A掉(早知道我也这么写了欲哭无泪)

不管了提升最重要A了也没啥用(嘴硬中),先来看题:

现在给你一个 n 行 m 列的矩阵,矩阵上每个格子有一个整数,其中第 i 行第 j 列对应的格子上的整数为 gi,j​​。

现在定义该矩阵的一个子矩阵 的快乐值为该子矩阵上的所有数字的异或和。

一组数字 a​1​​,a​2​​,...,a​n​​ 的异或和为 a1 xor a2 xor ... xor ana​1​​ xor a​2​​ xor ... xor a​n​​。(其中 xor 表示按位异或运算)

现在问你,该矩阵的所有子矩阵的快乐值之和为多少?

只记得24年做的时候做了很久,但是忘了正解是啥了(裂)

本题也是没有赛上,我们来把这个题搞明白

我们看到有一些测试点矩阵所有的元素都是1或者0,我们单独分析这种情况,这样子矩阵的异或和的规律出来了

子矩阵内1的个数为奇数的话子矩阵的异或和为1

为偶数则子矩阵异或和为0

总答案:多少个子矩阵内1的个数为奇数

我们的求解方式如下:

p1:枚举一个上界和一个下界,这个上界和下界会从整个矩阵中去处一个列数相同,行数为下界减上界加1的小矩阵,我们对于这个小矩阵,给它分成好多列,并设置c\[\]数组来统计每列的1的个数是奇数还是偶数,是1则为奇数,是0则为偶数,如cj=1表示在当前上下界限制中的小矩阵的第j列中1的个数为奇数,得到c\[\]数组

p2:对c数组求解一维前缀异或和

cpp 复制代码
循环1到j枚举列数
s[j]=c[j]^s[j-1]

得到s数组,在当前上界和下界限制中,对s\[\]求区间L,R的异或前缀和就是求解 行数为上界+下界-1行和列数为r-l+1列的子矩阵异或和,即

cpp 复制代码
s[x]=s[r]^s[l-1]

注意:24年的题解中也有声明,异或的逆运算是它本身,即连续运算两次相同值直接抵消,值不变

p3:那为了使我们的异或和为1,在选取L,R区间时,所求的异或和sR^sL-1中其中一者是1,则另一者必须为0,所以我们遍历s数组并固定sR,这样出现了两种情况

sR=1;那么前面出现sL-1=0的情况下,L,R对应子矩阵异或和为1,以R作为右界的子矩阵数量即为前面出现的0的数量

sR=0;那么前面出现sL-1=1的情况下,L,R对应子矩阵异或和为1,以R作为右界的子矩阵数量即为前面出现的1的数量

所以我们遍历 s,维护每一右界R前面出现过的 0 的数量cnt0、1 的数量cnt1,并根据R的情况计算它对答案的贡献,sR为1则其贡献为cnt0,sR为0则其贡献为cnt1,对贡献进行累加即为答案

(这边合法对子的数量可以直接乘法原理来做,一次性统计全部 pre 数组里 0 和 1 的总数相乘)

注意s0等于0,其贡献为1,所以cnt0初值为1,cnt1初值为0

那这是这个,我们部分样例的一个解题思路,我们现在拓展到完整问题

由于我们每个数最大为2的10次方,所以它的二进制表示至多10位

并且异或运算有一个关键原则,每个二进制位完全独立

所以我们这么解这个问题,k循环遍历0到10位,把矩阵里的数二进制中的第k位单独拿出来,我们可以获得一个0,1构成的与原来行列数矩阵,直接按照上文的方法进行求解,解出第k位的贡献

贡献怎么算?回顾一下,我们按照p1,p2,p3的方法解出了当前第k位取出的0,1矩阵中所有子矩阵的异或和,我们回顾单个子矩阵的异或和的概念显然只能这个和只能是0,1,那这第k位这一层的这一个子矩阵贡献就是求出的该子矩阵异或和(0,1)乘以位权,根据乘法分配律,位权都相同取出来,这一层贡献就是第k位这一层所有子矩阵的异或和之和乘以当前位权2^k,把所有层的贡献都累加起来获得答案

AC代码如下

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=303;
typedef long long ll;
int a[N][N],now[N],pre[N];
ll ans=0;
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	for(int u=1;u<=n;u++){
		memset(now,0,sizeof now);
		for(int d=u;d<=n;d++){
			for(int i=1;i<=m;i++){
				now[i]^=a[d][i];
			}
			for(int i=1;i<=m;i++){
				pre[i]=pre[i-1]^now[i];
			}
			for(int b=0;b<10;b++){
				int cnt[2]={1,0};
				for(int i=1;i<=m;i++)	cnt[pre[i]>>b&1]++;
				ans+=(cnt[1]*cnt[0]*(1ll<<b));
			}
		}
	}
	cout<<ans<<"\n";
}

五,总结

励志攻克第三四题,不要放弃希望,只要写了,总会中的

六,祝福

天天开心同学们

相关推荐
念何架构之路3 小时前
zap采样器与性能优化内幕
算法·性能优化·哈希算法
晒太羊的猫4 小时前
hot100动态规划刷题
算法·动态规划
weixin_531094695 小时前
heap_4内存管理
java·开发语言·算法
余额瞒着我当琳5 小时前
红黑树的实现--红黑树的概念、规则、效率、结构、旋转、查找、验证
数据结构·c++·算法
程序员南飞5 小时前
win系统安装minio
java·数据结构·算法
青少儿编程课堂5 小时前
AC自动机多模式串匹配精讲:校园关键词巡检实战解析
c++·python·算法·bfs·信息学竞赛
longlongzihan6 小时前
LeetCode 5. 最长回文子串:从暴力匹配到中心扩展的思维演进
c++·算法·leetcode
waoooqwe6 小时前
问卷样本真实吗
大数据·数据库·算法·数据分析
苦瓜打怪兽6 小时前
leetcode 热题100(11. 盛最多水的容器)
python·算法·leetcode