SWUFE2026新生赛题解

SWUFE2026新生赛题解

虽然在新生赛中表现的非常区,连B都没出,但还是艰难的在最后过题拿下了第一,既然作为第一,来写一下我的题解

K.谁想打xcpc?

作为本场比赛的签到题,本题也是非常的简单,所以为什么要让我们的主角的题目这么简单,主角不该逼格高点吗!
不多说,本题没很多要注意的点,不会输入只会输出也能写,下面直接贴上代码

#include<iostream>
using namespace std;
int main()
{
	cout<<"队友队友要不要和我打xcpc喵喵喵";
	return 0;
} 

F.经济差值

本题大致题意就是给你\(n\)个数字,找出最大值和最小值的差值,实现是比较简单的,洛谷也有一模一样的题P5724
完成这道题需要你会用循环,实现过程就是用两个变量存下最大值和最小值,然后最后输出两个的差就行了。
第一种方法,不需要管数据范围的方法就是先读入一个数,然后把最大值和最小值都设成第一个数,然后依次跟后面n-1个数比较,如果那个数更大就更新最大值,更小就更新最小值,实现如下

#include<iostream>
using namespace std;
int n;
int main()
{
	cin>>n;//先读入n
	int x,maxn,minn;//maxn表示最大值,minn表示最小值
	cin>>x;
	maxn=x;
	minn=x;//将最大值和最小值都设成第一个数
	for(int i=1;i<n;i++)//读入后面n-1个数
	{
		cin>>x;
		if(x>maxn) maxn=x;//如果当前数更大,更新最大值
		if(x<minn) minn=x;//如果当前数更小,更新最小值	
	} 
	cout<<maxn-minn;//输出最大值和最小值的差
	return 0; 
}

第二种方法是普遍被使用的方法,就是在一开始就把最大值设置的很小,最小值设置的很大,保证能被读入的数更新,看题的给的数据范围\(1\leq a_i \leq 10^9\),所以,我们把最小值设置成\(10^9+1\),最大值设成\(0\),就保证了一定会被更新,代码如下

#include<iostream>
using namespace std;
int n;
int main()
{
	cin>>n;
	int maxn=0,minn=1e9+1;
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>x;
		if(x>maxn) maxn=x;
		if(x<minn) minn=x;
	}
	cout<<maxn-minn;
	return 0; 
}

D.最大还是最小?

本题大致题意就是给你\(n\)行数,每一行有\(m\)个数,一行数的价值就是这一行中最大的数,让你求价值最小的有几行,并且求出是哪几行

本题相比于上一题,你需要学会数组的基本语法,我们用一个数组存下来有哪些是最小的,最后数组存了几个数就是第一个答案,数组里面的就是要的第二个答案,需要注意的是我们也要实时求出当前的最小值,如果有价值更小的,就把数组清空,重新开始放,如果价值跟之前最小的一样小,就把当前行放进数组里,如果比最小值大,就不用管了,需要注意的是,本题会出现负数,所以最大值不能像上一题一样初始值设成0,而是要设成一个比题目范围还小的数,代码实现如下

#include<iostream>
using namespace std;
const int MAXN=1e3+10;
int ans[MAXN];//ans就是存答案的数组 
int n,m;
int cnt;//cnt代表已经存了几个答案了 
int main()
{
	cin>>n>>m;
	int minn=1e9+1;//minn是全局最小值,原理同上一题 
	for(int i=1;i<=n;i++)
	{
		int x=-1e9-1;//x是当前行最大值,题目最小为-1e9,所以我们设置成-1e9-1 
		for(int j=1;j<=m;j++)//循环得到当前行最大值 
		{
			int t;
			cin>>t;
			if(t>x) x=t;
		}
		if(x>minn) continue;//如果当前行的最大值比最小值小,不用管 
		if(x<minn)//如果当前行的最大值比最小值小 
		{
			cnt=0;//cnt变成0代表数组里没有存值,可以理解为清空数组 
			minn=x;//更新最小值 
		}
		cnt=cnt+1;//cnt加一,代表多了一个答案 
		ans[cnt]=i;//多的这个答案是当前行,所以把编号存进去
	}
	cout<<cnt<<"\n";//cnt就是数组存的答案数量 
	for(int i=1;i<=cnt;i++) cout<<ans[i]<<" ";//把数组里存的答案输出出来 
	return 0;
}

E.序列变换

这题在赛场上想当然了,搞了个错误做法,导致我吃了五发罚时(还是太菜了)

首先说一下我想当然的想法,可能也有其他人跟我有一样的错误想法,我们读入n个数之后,从前往后扫,因为要保证单调递减,我们判断当前数是不是大于下一个数,如果不是,并且我们还有剩余的操作次数,就对这个数进行操作(乘2),然后操作次数-1,然后再判断当前数是否比下一个数大,只有两种情况会不是,第一就是我们操作之后还是不比下一个数大,此时因为每个数只能操作一次,所以不可能做到当前数比下一个数大,所以不满足,就是NO
第二种情况就是我们没有操作次数了,无法把这个数变得比下一个数大,所以也是NO,如果能够顺利的变化肯定就是YES了

大致一看这种做法貌似没什么问题,但这里面隐藏了一个陷阱,比如在前面\(a\)变大了两倍大于\(b\)了,但是后面b又进行操作来保证大于\(c\),就可能会导致\(a\)又小于\(b\)从而不满足题目条件,导致我们把NO输出成YES

那么我们该如何避免这种情况呢?其实很简单,我们过程不变,只需要从后往前变,因为我们从后往前变只会越来越大,前面的数变大不会影响我们的单调性,所以我们只要把循环改成从后往前就可以成功AC了!代码如下

#include<iostream>
using namespace std;
const int MAXN=2e5+10;
int num[MAXN];
int n,m;
int main()
{
	cin>>n>>m;
	bool ans=true;//ans记录最终答案是YES还是NO,刚开始默认NO 
	for(int i=1;i<=n;i++) cin>>num[i];
	for(int i=n-1;i>=1;i--)//从后往前扫 
	{
		if(num[i]<=num[i+1] && m>0)//如果当前数不比下一个大,并且还有操作次数 
		{
			num[i]*=2;//当前数变大,即对当前数进行操作 
			m--;//操作次数减一 
		}
		if(num[i]<=num[i+1]) ans=false;//如果当前数不大于下一个数,说明答案是NO,更新ans 
	}
	if(ans) cout<<"Yes";//如果ans是true,说明答案是Yes 
	else cout<<"No";//否则答案是No 
	return 0;
} 

J.石子游戏

本题我们需要重点抓住只能取一个石子这个限制,根据这个我们可以发现,每次我们可以选择将一个奇数堆变成偶数堆,或者将一个偶数堆变成奇数堆。

先观察什么时候我们必胜,这个比较容易看出来,显然场上只有一个奇数堆的时候,我们只要将这个变成偶数堆,我们就胜利了。但这种情况显然只能由一种状态变过来,就是有两堆奇数堆,有一堆被对方变成偶数堆,但是如果对方走最有策略肯定会把某个偶数堆变成奇数堆,这样我们就不能胜利了。但是由题意可知,0是偶数堆,此时无法再将偶数堆变成奇数堆,所以这样其他全为0,剩两个奇数的时候对方会迫不得已的让我们胜利,其他情况我们都不会胜利。

所以,根据上述,谁拿掉最后一个石子,让所有堆全部变成0,就胜利了。因为每次只能取一个,所以我们只需要计算一共有多少个石子,判断奇偶,如果是奇数,那么最后一个会被我们取完,就是YES,如果是偶数,那么最后一个会被对方取走,所以就是NO,唯一需要注意的细节是n堆石子的总数可能很多,所以总数要用long long类型的变量存,代码如下

#include<iostream>
using namespace std;
int n;
int main()
{
	cin>>n;
	long long sum=0;//用sum记录总共有多少个石子,总数可能很大,用long long类型 
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>x;
		sum+=x;//sum加上每一堆的石子,就是总数了 
	}
	if(sum%2==1) cout<<"Yes";//如果是奇数,我们胜利
	else cout<<"No";//如果是偶数,对方胜利
	return 0; 
} 

L.跳跃得分

作为我6min拿了一血的题,我觉得这道题出的非常好!

这道题我们需要用到一种思想,名为动态规划!对于这道题,不是很难

首先我定义一个数组,dp[i]代表跳到第i个位置最多能拿到多少分,因为我们可以从任何位置开始跳,所以dp[i]的初始值就是\(a_i\),又因为只能从前往后跳,所以如果我们从前往后算,那么前面每一个能跳到当前点的位置都已经被计算过了,在前面那些值取最大值就是跳到当前位置的最大值了,然后再往后跳就行了,因为我们是跳到n之后结束,所以我们从最后结束的dp数组里取最大值就是答案了,唯一要注意的细节就是dp数组是很多个相加,所以有可能超过int的范围,注意用long long存,代码实现如下

#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=2e5+10;
int n;
long long dp[MAXN];//dp可能会超int所以用long long 
int num[MAXN];
inline void Solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>num[i];
	for(int i=1;i<=n;i++) dp[i]=num[i];//dp[i]初值为num[i] 
	for(int i=1;i<=n;i++)//从前往后扫 
	{
		if(i+num[i]>n) continue;//如果跳到后面了,就不管 
		dp[i+num[i]]=max(dp[i+num[i]],dp[i]+num[i+num[i]]);
		//i+num[i]就是当前点能跳到的位置
		//如果从当前点跳到那个点能得到的值更大,就更新值 
	}
	long long ans=0;//注意用long long 
	for(int i=1;i<=n;i++) ans=max(ans,dp[i]);//ans取最大的一个dp值,就是最终答案 
	cout<<ans<<"\n";
	return ;
}
int main()
{
	int T;//T组数据,所以要跑T遍,用函数封装 
	cin>>T;
	while(T--) Solve();
	return 0;
}

B.守护地脉石

题意略,本题的所用到的算法叫做贪心,有兴趣的可以去自行了解,这里不多赘述

对于这题,我们需要优先击杀\(\frac{a_i}{b_i}\)更大的魔物,所以我们先排序,将\(\frac{a_i}{b_i}\)大的排在前面,然后依次击杀就行了,下面给出证明

考虑两只魔物\(x\)和\(y\)
1.先击杀\(x\)后击杀\(y\)的消耗为\(b_x*(a_x+a_y)+b_y*a_y\)
2.先击杀\(y\)后击杀\(x\)的消耗为\(b_y*(a_x+a_y)+b_x*a_x\)

化简可得
1.\(b_x*a_x+b_x*a_y+b_y*a_y\)
2.\(b_y*a_x+b_y*a_y+b_x*a_x\)

消掉相同的项,可得

1.\(b_x*a_y\)
2.\(b_y*a_x\)

所以方案1更优即\(b_x*a_y < b_y*a_x\)即\(\frac{a_x}{b_x}> \frac{a_y}{b_y}\)
同理,方案二更优即\(\frac{a_y}{b_y} > \frac{a_x}{b_x}\)

所以,先消除\(\frac{a_i}{b_i}\)更大的是更优解,证毕

代码上,需要注意的细节是由于double的精度问题,我们不能直接通过除来排序,要通过十字相乘转化,即将\(\frac{a_x}{b_x}> \frac{a_y}{b_y}\)转化成\(a_x*b_y>a_y*b_x\)

具体代码实现如下

#include<iostream>
#include<algorithm>//这个头文件包含了排序函数sort 
using namespace std;
const int MAXN=1e5+10;
struct node
{
	long long x,t;//相乘之后可能会超过int,所以用long long 
};
node num[MAXN];
int n;
inline bool cmp(node a,node b)//自写sort排序规则函数 
{
	return a.t*b.x<a.x*b.t;//十字相乘排序 
}
inline void Solve()
{
	long long sum=0;//和可能超过int,所以用long long 
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>num[i].x;
		sum+=num[i].x;//累加所有a,得到每秒造成的伤害 
	}
	for(int i=1;i<=n;i++) cin>>num[i].t;
	sort(num+1,num+1+n,cmp);
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		ans+=num[i].t*sum;//加上打败当前怪物时怪物造成的伤害 
		sum-=num[i].x;//当前怪物已被打败,减去他的伤害 
	}
	cout<<ans<<"\n";
	return ;
}
int main()
{
	int T;//多组数据,函数封装运行多次 
	cin>>T;
	while(T--) Solve();
	return 0;
 } 

G.短期记忆与长期记忆

本题不涉及任何算法,只需要根据题意模拟就行了,这里我采用了STL容器string来储存字符串,然后每次直接算出这个字符串的压缩值,这样我们就有每个字符串和他对应的压缩值

对于每次的查询,我们只需要查询新的c个里面有没有一样的字符串,没有再查询剩下的压缩值中有没有符合题意的就行,关键在于代码上的实现,这里直接贴上代码

#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
const int MAXN=4005;
string s[MAXN];//开string数组存所有的字符串 
int h[MAXN];
int cnt;//cnt代表现在存了几个字符串了 
int n,c,T;
inline void insert()//插入时进行的操作 
{
	cin>>s[++cnt];//输入新的字符串 
	for(int i=0;i<s[cnt].size();i++)//根据题意得到该字符串的压缩值 
	{
		h[cnt]=(h[cnt]+s[cnt][i])%256;
	}
	return ;
}
inline void query()//查询时进行的操作 
{
	string x;
	cin>>x;
	int res=0;
	for(int i=0;i<x.size();i++) res=(res+x[i])%256;//得到要查询的字符串的压缩值 
	int flag=0;
	for(int i=max(cnt-c+1,1);i<=cnt;i++) if(s[i]==x) flag=i;
	//最新的c个就是从cnt-c+1开始,如果总数都不到c个,就从1开始找
	//每次找到一样的就更新位置,最后会得到最后面的一样的位置
	//就是题目上的最新的 
	if(flag) 
	{
		cout<<"SHORT "<<flag<<"\n";
		return ;
	}
	//如果短期记忆没找到一样的,从前面的找压缩值 
	int minn=500;
	flag=0;
	for(int i=1;i<=cnt-c;i++)//长期记忆从1开始找到cnt-c,保证了先找编号更小的 
	{
		int t=res-h[i];//得到当前与要查询的之间的差值 
		if(t<0) t=-t;//取绝对值 
		if(t>=T) continue;//如果差值比要求大,忽略 
		if(t<minn)//如果差值更小,更新差值 
		{
			flag=i;//更新答案编号 
			minn=t;//更新更小差值 
		}
	}
	if(flag) cout<<"LONG "<<flag<<" "<<minn<<"\n";
	else cout<<"NONE\n";
	return ;
}
int main()
{
	cin>>n>>c>>T;
	for(int i=1;i<=n;i++)
	{
		int opt;
		cin>>opt;
		if(opt==1) insert();
		else query(); 
	}
	return 0;	
} 

I.走格子

本题跟上一题差不多,都是没什么深邃的思想,最大的难点是代码上的各种小细节,只需要跟着题目走就行了,下面说一下几个比较重要的细节

1.格子不能重复走,所以用一个vis数组存下来每个格子是否已经走过

2.对于翻转后的格子,我们用映射,因为H,V只有相互变换,所以我们设成0和1,这样只要异或1就可以互相变换,用一个cnt保存变化了几次,就是异或几次1
对于1,2,3,4,我们可以发现他们其实就是在%4意义下加一,所以我们把他们分别设成2,3,4,5,这样就是%4加2了,所以同样用一个cnt记录变化几次就行了

3.因为要经过所有的黄色格子,所以我们先用cnt存下来一共有几个黄色格子,然后在走的时候记录走过几个黄色格子,因为不会重复走,所以走过黄色格子个数等于cnt时就经过了所有的黄色格子
代码如下

#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=20;
char ma[MAXN][MAXN];
int n,m;
bool vis[MAXN][MAXN];
int sx,sy;
int cnt;
int cnt1,cnt2;
bool ans;
inline bool Check(int x,int y,int op)//x,y代表哪个格子,op代表从哪个方向走过来 
{
	//1,2,3,4,分别代表从下,左,上,右走进来的 
	if(ma[x][y]=='.' || ma[x][y]=='#' || ma[x][y]=='Y' || ma[x][y]=='S' || ma[x][y]=='R' || ma[x][y]=='T') return true;
	//这些格子没有走进去的条件 
	if(ma[x][y]=='H' || ma[x][y]=='V')//两个格子分别只能从上下和左右进来 
	{
		//刚好对应了op为奇数和op为偶数 
		int type;
		if(ma[x][y]=='H') type=0^cnt1;
		else type=1^cnt1;
		if(type)
		{
			if(op%2) return true;
			else return false;
		}
		else
		{
			if(op%2) return false;
			else return true;
		}
	}
	else
	{
		int type=((ma[x][y]-'1'+cnt2)%4)+2;
		//根据格子1,2,3,4,对应的类型判断能不能走进来就行 
		if(type==2)
		{
			if(op==3 || op==4) return true;
			else return false;
		}
		if(type==3)
		{
			if(op==4 || op==1) return true;
			else return false;
		}
		if(type==4)
		{
			if(op==1 || op==2) return true;
			else return false;
		}
		if(type==5)
		{
			if(op==2 || op==3) return true;
			else return false;
		}
	}
}
inline void Move(int x,int y,int op,int t);//移动函数,具体内容在下面 
void DFS(int x,int y,int t)//x,y代表是那个格子,,t代表经过了几个黄色格子 
{
	if(x<1 || x>n) return ;
	if(y<1 || y>m) return ;//x,y不在题目范围内,直接跳过 
	if(vis[x][y]) return ;//已访问,跳过 
	if(ma[x][y]=='#') return ;//障碍格,跳过 
	vis[x][y]=true;//标记已走过 
	if(ma[x][y]=='T')//走到终点了 
	{
		if(t==cnt) ans=true;//经过了所有黄色格子,说明存在合法路线 
		vis[x][y]=false;//标记未走过,然后返回 
		return ;
	}
	if(ma[x][y]=='S' || ma[x][y]=='.' || ma[x][y]=='Y'||ma[x][y]=='R')//可以向任意一个方向走的时候 
	{
		if(ma[x][y]=='Y') t++;//如果是黄色格子,经过的数量加一 
		if(ma[x][y]=='R') //如果是翻转个,翻转次数加一 
		{
			cnt1^=1;
			cnt2=(cnt2+1)%4;
			//两个cnt分别是异或1的条件和%4意义下加1 
		}
		//check函数是能不能从这里走到下一个格子 
		if(Check(x+1,y,3)) DFS(x+1,y,t);
		if(Check(x-1,y,1)) DFS(x-1,y,t);
		if(Check(x,y+1,2)) DFS(x,y+1,t);
		if(Check(x,y-1,4)) DFS(x,y-1,t);
	}
	else//如果是有条件的格子 
	{
		if(ma[x][y]=='H') Move(x,y,0^cnt1,t);
		else if(ma[x][y]=='V') Move(x,y,1^cnt1,t);//H和V是异或1的意义下 
		else Move(x,y,((ma[x][y]-'1'+cnt2)%4)+2,t);//数字是%4加2转化 
	}
	if(ma[x][y]=='R')//如果是翻转格,返回时回退当前的翻转 
	{
		cnt1^=1;
		cnt2=(cnt2+3)%4;
	}
	vis[x][y]=false;//返回时要标记该格子未走过 
	return ; 
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>ma[i][j];
			if(ma[i][j]=='S')//记录起点 
			{
				sx=i;
				sy=j;
			}
			if(ma[i][j]=='Y') cnt++;
		}
	}
	DFS(sx,sy,0);//从起点开始走,走过了0个黄色格子 
	if(ans) cout<<"YES";
	else cout<<"NO";
	return 0;
} 
inline void Move(int x,int y,int op,int t)//x,y,t与DFS意义相同,op是要走的类型 
{
	//0,1代表HV
	//2,3,4,5代表1,2,3,4,根据题意走就行 
	if(op==0)
	{
		if(Check(x,y-1,4)) DFS(x,y-1,t);
		if(Check(x,y+1,2)) DFS(x,y+1,t);
	}
	if(op==1)
	{
		if(Check(x-1,y,1)) DFS(x-1,y,t);
		if(Check(x+1,y,3)) DFS(x+1,y,t);
	}
	if(op==2)
	{
		if(Check(x-1,y,1)) DFS(x-1,y,t);
		if(Check(x,y+1,2)) DFS(x,y+1,t);
	}
	if(op==3)
	{
		if(Check(x,y+1,2)) DFS(x,y+1,t);
		if(Check(x+1,y,3)) DFS(x+1,y,t);
	}
	if(op==4)
	{
		if(Check(x+1,y,3)) DFS(x+1,y,t);
		if(Check(x,y-1,4)) DFS(x,y-1,t);
	}
	if(op==5)
	{
		if(Check(x,y-1,4)) DFS(x,y-1,t);
		if(Check(x-1,y,1)) DFS(x-1,y,t);
	}
	return ;
}
posted @ 2026-10-01 20:04  罗小菜  阅读(36)  评论(0)    收藏  举报