CSP-J复赛模拟赛3 王晨旭补题 2026.10.4

一,前言

24年第3次模拟补题

感觉不大行啊,瓶颈了,记忆中24年做的时候第四题非常难,所以本次先押了第三题,发现不大行啊,尝试打第四题的暴力,直接喜提零蛋,只能说T3,4阻力依旧很大

今天的补题尽量正经一些,多说题相关的内容

二,成绩

1.环形储物柜【100/100】嗯这波卡了兄弟

2.是否同构【100/100】恨意从未消退

3.书籍调序【20/100】大妈你谁

4.社恐的聚会【0/100】我没捐啊神特喵比前年还低

总分【220/400】今日班级Rank8,榜一题四满分,老师官方认证的这套题难

题一稍微卡了一会,题二做的倒是不慢,三四题真实难以跨越的鸿沟

三,题二

在这里对前年的内容进行一些补充吧

重放题面

有两个长度为 n 的数组 a,b。如果存在一个整数 k,满足:

0≤k≤

并且保持数组 b 不动,将数组 a 的前 k 项与后 k 项对应交换,也就是对于所有 1≤i≤k,交换 ai​​ 与 a​n−k+i​​,交换后得到的数组恰好等于 b,则称数组 a 和数组 b 同构。

给定多组数组 a,b,请分别判断它们是否同构。

这是考场注释

//检查方法,a所有元素互不相同,b中的元素一定有一个唯一的ai与之对应

//要使b与a完全重合,b1对应的唯一的ai必须要换到a1,且i位置若换到1位置的话,每个i会对应一个唯一的k

//换完检查合法性即为答案

a:1 2 5 6 3 4

b:3 4 5 6 1 2

3(b1)对应一个唯一的a5,a5必须换到a1,唯一对应的k为2(6-5+1),为1或3或其他均无法将a5换到a1

看完这个基本这个题就结束了

回看24年心态真是够差的,今年反而呢淡了一些吧

四,题三

去年挑战csp-s第一题的时候犯了相同的错误,两个题都是本质贪心,我却写了两个DP

这一次的赛上放24年绝对得崩溃,但其实今年的表现也没有好多少

先放题面

书架上一开始从左到右摆放了 n 本书,第 i 本书的编号为 a​i​​。老师希望把书架调整成目标顺序 b1,b2,...,bn。

每次操作可以选择当前书架上的任意一本书,并将它移动到书架最前面。

如果两个序列包含的书不完全相同,则无法完成调整,输出 -1;否则请计算最少需要执行多少次操作。

编号相同的书可以看作完全相同的书。

好吧,初始思路还是骗分(本质老鼠),我的思路是求最长公共子序列LCS,感觉脑子已经有点迷糊了,我一写代码,直接就发现了3个问题:

1,由于对这个算法不够熟悉,不知道它的dp写法(不转LIS+二分)没有优化方法,最优时间复杂度为O(n方),导致我还沉浸于先写一个普通的再慢慢优化的幻想,数据范围卡掉O(n方)也证明了LCS不是这个题的答案

2,不会写LCS,这个事也是直接影响了我好一会,我没有因为考到这个但发现忘了不会写这个心态炸掉,而是好不容易又手推又回忆的终于写出了完全正确的算法,却突然发现这个题用不了LCS的时候心态才炸,也自然改不动了,一直觉得LCS是对的尝试在它上面优化,最终失败

3,过不了样例的时候没有选择放弃,3个题面样例都过了,但我自己手推的样例怎么着都过不了都写不出来,一直觉得是我没考虑到一些情况,但没想到这是算法上的硬伤

包括还有脑子推自己的样例很难得到正确答案等诸多问题

除了心态和状态,我们现在来分析一下题目本身:简单贪心

把书分为两类,一类是被移动过的书(一次到多次)(集合T)

还有一类自始至终都没有被移动过的书(集合S)

而集合S不动,相对顺序永远不变

因为往前放的原则,所有被移动的书一定会出现在没有被移动的书的前面,

即有最终序列满足 T重新排列+S顺序不变

集合S必须是目标序列的一段后缀,这就是它和LCS的本质区别,并不是值一样就行了

LCS只要求:选出的元素在 a 和 b 中相对顺序一致;不要求选出的元素是 b 的后缀

最关键的是存在某个 k,使得(并且 S 在原数组 a 中出现的相对顺序和 b 里面一致):

本题要求,b里的公共子序列必须要是b的末尾连续一段,贪心策略就是让不动集合S最大,所以就是找最大的k,建议使用双指针

T自然不用多说,不管要求的前缀排列是什么,都可以通过改变T中元素移动的顺序来得到,这里面的元素显然不能不动,又最优每个元素移动一次(只做最后一次到位的移动,可以通过顺序来调整),所以T中元素个数即为答案

AC代码:注意无解判断(真的短啊好简单)

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+5;
int a[N],b[N],dp[N][N],aa[N],bb[N];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		aa[i]=a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
		bb[i]=b[i];
	}
	sort(aa+1,aa+n+1);
	sort(bb+1,bb+n+1);
	for(int i=1;i<=n;i++){
		if(aa[i]!=bb[i]){
			cout<<-1<<"\n";
			return 0;
		}
	}
	int r=n,l=n,cnt=0;
	while(l>0){
		if(a[l]==b[r]){
			r--;l--;
			cnt++;
		}
		else{
			l--;
		}
	}
	cout<<n-cnt;
}

五,题四

真正的魔王来了,但是,魔王其实也是小萝莉

仅输出-1---------20分

仅输出------------65分

我滴妈加起来85分了你直接给我A了算了

不说别的今天说好要正经,直接看题,必须解决这个萦绕在我心里两年的梦魇

有 n 个患有社交恐惧症的人想参与一个聚会,但是这个聚会只有两张桌子。每张桌子上的任意两个人都必须相互认识。

认识关系不一定对称,也就是说,可能第 i 个人认识第 j 个人,但第 j 个人并不认识第 i 个人。只有两个人互相都认识时,他们才可以被安排在同一张桌子上。

允许某一张桌子为空。

请判断能否将所有人安排到两张桌子上,使得每张桌子内部任意两个人都是相互认识的。如果可以,请进一步求出两张桌子中人数最多的那张桌子的最少人数。

前年光顾着装逼了把题解粘贴上去啥也看不懂(这个题并不是二分最小化答案),现在我们详细说说这个题到底怎么做

拓展:二分图

我们先明确一下二分图的定义

一个无向图,如果可以把所有顶点分成两个互不相交的集合 (X,Y),满足:

图里所有的边,只能跨集合连接(X 连到 Y);同一个集合内部,没有任何边。

判定是找奇环(本来初赛就应该学的但我啥也不知道)

一个无向图是二分图,当且仅当图中不存在长度为奇数的环(奇环)

好有了二分图这样的一个先导知识,我们来做这道题

p1:(神秘p1回归)首先建图,我们把所有不互相认识的人之间连一条无向边,这条边是为了标记哪两个人不可以放到同一张桌子上的,打dfs进行黑白染色法,无向边一端染黑色,一端染白色,染黑色1的放在桌1(或者桌2都无所谓),染白色0的放在桌2(也可以是桌1)这样根据二分图的判定,有奇环(染色失败)的情况直接无解,顺便可以想象一下

我和你互相不认识,咱俩不能在一个桌 我染黑色你染白色

你和他互相不认识,你俩不能在一个桌 你染白色他染黑色

他和我互相不认识,我他不能在一个桌 我染黑色他染白色

怎么有人既染白色也染黑色啊

(喵的一共就俩桌还分个屁啊(bushi))

p2:这样我们对于每个每个连通块都染好色了,连通块和连通块之间没有边,证明没有不能同桌的限制,我们把每个连通块视为两个物品,把其中一张桌子作为当前的收益,对一个连通块的两个物品,只能选择其中一个,这就是一个分组背包

卡了一会但又觉得很蠢的三个问题

你怎么知道选完这个连通块,下一个连通块可以随便选,不需要考虑连通块和连通块之间的不合法情况吗?(我真想过)

显然我们的建图方式直接决定了只要两点之间没有边,这两点一定有坐在同一个桌子上的合法性,连通块和连通块之间呢互不影响
为什么一个连通块内只能二选一?

我们的染色规则决定了一个连通块会被分为两个组(二分图),你选了一个组,另一个组显然呢就不能选了,那又不能一个组只选部分人,那剩下一部分人显然会和另一组的人在一块,这是非法的,所以只能整组整组的二选一
为什么这一个连通块染黑色的和那一个连通块染白色的可以坐在一块?

我们染的色不是真正意义上的染色,而是将一个连通块内的人分为两个组,这两个组内部互斥,与外部的组没有任何的关系,跨块没有任何限制,所以每个连通块的选择完全独立

这个块选A组,那个块选B组或者A组都没有关系

明确这三个问题,我们现在来愉快地dp吧

可行性dp转移时常常使用 |

先看一下dpj的含义

dpj=1处理完若干个连通块后,有方案可以能够使第一张桌子上坐满j个人

dpj=0处理完若干个连通块后,没有任何方案可以使第一张桌子上坐满j个人

那我们第二张桌子上的人数就是n-j

再看一下转移

对于每个块的两个组,组a和组b来说,我们一共有两种选择

第一种选择,第一张桌子坐组a的人

第二种选择,第一张桌子坐组b的人

我们额外设立dp2,这边是状态副本法,在每次内部循环的时候,dpi是存储的还没有考虑这个连通块的时候可行性,dp2存储该连通块对状态产生的影响,也就是新产生的可行性

i表示没有考虑这个连通块的时候,第一张桌子上有i个人

选择a组:dp2i+a |=dpi

选择b组:dp2i+b |=dpi

那么此时问题来了,为什么要有dp2,还是那个问题,你要在新状态(考虑这个连通块之后)上继承,还是在旧状态(没有考虑这个连通块)上继承,dp2不自继承,dp也不自继承,在这个状态中,你选择了A组,后续状态的更新,你显然不会再基于这个选A组的状态来更新了!而是基于你没选任何东西的状态再继承,不然像连续选同连通块内两个A组,一个A组一个B组的非合法情况都出来了,这一点可以与昨天能重复选的背包进行区分

倒序循环滚动数组的写法似乎可以?保证了更新一个状态后下一次更新不会基于这个状态再更新,但其实不行,因为这样的话无法保证每组内至少选一个,于是放弃

答案的话呢,如果dpj=1证明第一个桌子放j个人可行,这样直接循环比较在j和n-j的大值(两张桌子中人多的那一张)里面求最小值即有答案

AC代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=520;
int f[N][N],dp[N],dp2[N];
vector<int>mp[N];
vector<pair<int,int> >v;
int col[N],flg=1,c0,c1;
void dfs(int x,int c){
	col[x]=c;
	if(c==0)	c0++;
	if(c==1)	c1++;
	for(auto i:mp[x]){
		if(col[i]==-1)	dfs(i,c^1);
		else if(col[i]==c)	flg=0;
	}
}
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>f[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			if(f[i][j]==0||f[j][i]==0){
				mp[i].push_back(j);//单方认识和互不认识都可以啊 
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(col[i]==-1){
			c0=c1=0;
			dfs(i,0);
			v.push_back({c0,c1});
		}
	}
	if(flg==0){
		cout<<"No\n";
		return 0;
	}
	cout<<"Yes\n";
	dp[0]=1;
	for(pair<int,int> x:v){
		int a=x.first,b=x.second;
		memset(dp2,0,sizeof dp2);
		for(int i=0;i<=n;i++)	dp2[i+b]|=dp[i],dp2[i+a]|=dp[i];
		memcpy(dp,dp2,sizeof dp);
	}
	int ans=n;
	for(int j=0;j<=n;j++){
		if(dp[j]){
			ans=min(ans,max(j,n-j));
		}
	}
	cout<<ans<<endl;
}

六,总结

挺好的,今天解决了前年看都不敢看的题,有一种莫名的成就感啊,分数已经不重要了,心态最重要啊,挺喜欢一个博主说的话:"没出没事,出了好事",但同时也要紧迫一些,做到松弛有度,明天的比赛要认真对待

七,祝福

祝稳稳的幸福

相关推荐
H.莓飛44 分钟前
【数据结构】堆_OJ题
linux·数据结构·算法
Dream it possible!1 小时前
蓝桥杯_翻转_C++
c++·算法·蓝桥杯
Ivanqhz1 小时前
BURG(自底向上重写生成器)
服务器·数据库·人工智能·深度学习·算法
言乐61 小时前
逻辑回归与利弊
人工智能·算法·机器学习·数据挖掘·逻辑回归
海绵宝宝转agent1 小时前
Leetcode100 二叉树的中序遍历
java·算法
根目录下的猫1 小时前
ROS 2 C++(rclcpp)版本,实现话题订阅、话题发布,以及完整编译运行流程
开发语言·c++·算法
大侠归来3 小时前
C 语言 | 在函数中操作数组
c语言·c++·算法
怕浪猫8 小时前
GEO 优化到底是什么?AI时代内容创作者必须懂的新技能
算法·面试·github
朝朝辞暮i9 小时前
C++ 第 23 课:class —— 开始真正进入面向对象
开发语言·c++·算法