21级爪哇程序设计新生赛(二)题解

21级爪哇程序设计新生赛(二)

  • A 小爪的数字集合(并查集)
  • B 小爪的得分(博弈)
  • C 小爪的博弈(博弈)
  • D ljc和cyj玩五子棋(模拟)
  • E ljc和雪球(模拟)
  • F LJC的背包(动态规划)
  • G 最小公倍数(数学)
  • H 小爪的质因数(数学)
  • I LJC搭积木(动态规划)
  • J CYJ走迷宫(简单搜索)
  • K 经典皇后问题(简单搜索)
  • L 隔离人数(并查集)
  • 总结

这次的新生赛题目以思维题为主,涉及的知识点与第二轮考核一致,主要有:数学模拟基础博弈动态规划简单搜索并查集。而我们出题是按照每个知识点出两道题,一道模板题,一道中等难度的题目。因为有模板题,如果你是在网上搜到了题目,然后直接去复制粘贴的话那就没意思了。
模板题是为了检验大家是否学习了相关知识点,中等难度的题目是为了看大家是否能够灵活运用相关知识点。当然如果没做出模板题的,我的建议是先学习相关知识,可以看视频、刷博客,然后再去了解别人的模板题是怎样解的,再去做。本次新生赛更多的只是为了查漏补缺,了解自己在哪些方面需要加强。当然如果你有比题解更巧妙的思路,欢迎来找师兄师姐讨论!

A 小爪的数字集合(并查集)

21级爪哇程序设计新生赛(二)题解_第1张图片
题解:并查集模板题,没啥好说的,没有学过并查集的同学可以去学一下。
只需看看两个数是否在同一个集合中,不在就放到同一个集合里

#include
using namespace std;
const int N=1e6+5;
int a[N];
int find(int x)
{
	if (a[x]!=x) a[x]=find(a[x]);
	return a[x];
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) a[i]=i;
	while(m--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		x=find(x);
		y=find(y);
		if (x!=y)
		{
			a[x]=y;
		}
	}
	int res=0;
	for (int i=1;i<=n;i++) if (a[i]==i) res++;
	printf("%d\n",res);
	return 0;
}

B 小爪的得分(博弈)

21级爪哇程序设计新生赛(二)题解_第2张图片

题解:要站在学生的角度去想,对于学生最有利的肯定是x>y,所以不管小爪的数是多少,学生每次都说一个最大的数肯定是不亏的,所以小爪是一分都得不到的

#include
using namespace std;
int main()
{
	int n;
	scanf("%d",&n);
	printf("0.0000\n");
	return 0;
}

C 小爪的博弈(博弈)

21级爪哇程序设计新生赛(二)题解_第3张图片

题解:要想最后组装成的数大,那肯定得要取的1多才行,所以两个人肯定都是先取都是1的位置(让别人少一个1)然后再取自己是1的位置,最后看看谁能取到的1多就行了

#include
#include
#include
using namespace std;
const int N=1e6+5;
int t1,t2,t3;
char s1[N],s2[N];
int main()
{
	int n;
	scanf("%d",&n);
	scanf("%s%s",&s1,&s2);
	for (int i=0;i<2*n;i++)
	{
		if (s1[i]==s2[i] && s1[i]=='1') t3++;
		else if (s1[i]=='1') t1++;
		else if (s2[i]=='1') t2++;
	}
	int flag=1;
	if (t1>t2) flag=1;
	else if (t3%2==0 && t1+2<t2 || t3%2==1 && t1+1<t2) flag=2;
/*
(后面的说的有点绕,可以自己模拟一下)
如果t3%2==0说明都是1的位置两人都能拿相同的数量,==1时说明小爪能多拿1个都是1的位置
t1+2或者t1+1是指当t1
	else flag=3;
	if (flag==1) printf("First");
	else if (flag==2) printf("Second");
	else printf("Draw");
	return 0;
}

D ljc和cyj玩五子棋(模拟)

21级爪哇程序设计新生赛(二)题解_第4张图片
题解:可能题目有点长,但看完就会发现数据较小,只需看看当前棋子放下时是否获胜即可,所以我写的就比较简单了,直接复制了几个while循环去判断横竖左斜右斜是否满足大于5个同颜色的棋子连起来即可。(写的有点长,有更优的方法可以跟我讨论一下,ljc肯定是输的)

#include
using namespace std;
int f[12][12];
bool check(int x,int y,int t)
{
	int i,j;
	int res=1;
	i=x-1;
	while (i>0 && f[i][y]==t)
	{
		i--;
		res++;
	}
	i=x+1;
	while (i<11 && f[i][y]==t)
	{
		i++;
		res++;
	}
	if (res>=5) return true;
	res=1;
	i=y-1;
	while (i>0 && f[x][i]==t)
	{
		i--;
		res++;
	}
	i=y+1;
	while (i<11 && f[x][i]==t)
	{
		i++;
		res++;
	}
	if (res>=5) return true;
	res=1;
	i=x-1;j=y-1;
	while (i>0 && j>0 && f[i][j]==t)
	{
		i--;j--;
		res++;
	}
	i=x+1;j=y+1;
	while (i<11 && j<11 && f[i][j]==t)
	{
		i++;j++;
		res++;
	}
	if (res>=5) return true;
	res=1;
	i=x-1;j=y+1;
	while (i>0 && j<11 && f[i][j]==t)
	{
		i--;j++;
		res++;
	}
	i=x+1;j=y-1;
	while (i<11 && j>0 && f[i][j]==t)
	{
		i++;j--;
		res++;
	}
	if (res>=5) return true;
	return false;
}
int main()
{
	int t,n;
	scanf("%d%d",&t,&n);
	for (int i=0;i<12;i++)
		for (int j=0;j<12;j++) f[i][j]=-1;
	int flag=2;
	while(n--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		x++;y++;
		f[x][y]=t;
		if (check(x,y,t))
		{
			flag=t;
			break;
		}
		t++;
		t%=2;
	}
	if (flag==0) printf("hhhh cyj");
	else if (flag==1) printf("hhhh ljc");
	else printf("ji xu ba");
	return 0;
}

E ljc和雪球(模拟)

21级爪哇程序设计新生赛(二)题解_第5张图片
题解:没啥好说的,就题目怎么说就怎么做就行了。用循环模拟雪球下落,每次增加当前高度的重量,然后碰到石头就减,如果小于0就变成0

#include
using namespace std;
int main()
{
	int w, h, u1, d1, u2, d2;
	scanf("%d%d%d%d%d%d",&w,&h,&u1,&d1,&u2,&d2);
	while (h > 0)
	{
		w += h;
		if (h == d1)
			w -= u1;
		else if (h == d2)
			w -= u2;
		if (w < 0)
			w = 0;
		h--;
	}
	printf("%d",w);
	return 0;
}

F LJC的背包(动态规划)

21级爪哇程序设计新生赛(二)题解_第6张图片动态规划中最经典的01背包模板题,没啥好说的,如果没学过的同学可以在B站搜索大雪菜的闫氏dp分析法,dp入门一下,然后再做(学)几个背包的模板题

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int N=1e3+5;
int dp[N];
int main()
{
    int n,V;
    cin>>n>>V;
    for (int i=0;i<n;i++)
    {
        int w,v;
        cin>>v>>w;
        for (int j=V;j>=v;j--)
        {
            dp[j]=max(dp[j],dp[j-v]+w);
        }
    }
    cout<<dp[V]<<endl;
    return 0;
}

G 最小公倍数(数学)

21级爪哇程序设计新生赛(二)题解_第7张图片
题解:用欧拉筛(线性筛)筛出质数,然后枚举区间中的数看看是否是素数,素数的最小公倍数就是他们的乘积(因为两两间的最大公约数为1)

#include
#include
#include
#include
using namespace std;
const int N = 1e6 + 5;
const int mod = 1e9+7;
bool is[N];
int prime[N];
int nn = 0;
void Eurle()
{
	memset(is,true,sizeof(is));
	is[0]=false;
	is[1]=false;
	for (long long i=2;i<=1e6;i++)
	{
		if (is[i]) prime[++nn] = i;
		for (long long j=1;j<=nn && i*prime[j]<=1e6;j++)
		{
			is[i*prime[j]]=false;
			if (i%prime[j]==0) break;
		}
	}
}
int main()
{
	Eurle();
	int l, r;
	scanf("%d%d",&l,&r);
	bool flag=false;
	long long ans = 1;
	for (int i=l;i<=r;i++)
	{
		if (is[i])
		{
			ans=ans*i%mod;
			flag=true;
		}
	}
	if (flag) printf("%lld",ans%mod);
	else printf("-1");
}

H 小爪的质因数(数学)

21级爪哇程序设计新生赛(二)题解_第8张图片
方法一:筛出质数,然后枚举质数,求数量
方法二:直接枚举从2到sqrt(x)中的数,再求数量
这里给出方法二的代码(方法1就是将for循环中的枚举变成筛出的质数就行了)

#include
using namespace std;
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int x;
        cin>>x;
        for(int i=2;i<=x/i;++i)
        {
            if(x%i==0)
            {
                int cnt=0;
                while(x%i==0)
				{
					cnt++;
					x/=i;
				}
                printf("%d %d\n",x,cnt);
            }
        }
        if(x>1) printf("%d %d\n",x,1);
        printf("\n");
    }
    return 0;
}

I LJC搭积木(动态规划)

21级爪哇程序设计新生赛(二)题解_第9张图片
题解:想到用dp的话就很简单了。
首先我们先定义f[i]表示以i结尾的已经拼接了的字符串的最大长度。
那么每次枚举一个字符串s,a表示以a结尾(s以a开头),b表示以b结尾,那么以b的最大长度就是以a结尾的最大长度加上s的长度和以b结尾的最大长度中的最大值
状态转移方程:f[b]=max(f[b],f[a]+(int)s.length());

#include
#include
#include
using namespace std;
int f[27];
int main()
{
        int n;
        cin>>n;
        int res=0;
        memset(f,0,sizeof(f));
        for (int i=0;i<n;i++)
        {
            string s;
            cin>>s;
            int a=s[0]-'a',b=s[s.length()-1]-'a';
            f[b]=max(f[b],f[a]+(int)s.length());
            res=max(res,f[b]);
        }
        cout<<res<<endl;
    return 0;
}

J CYJ走迷宫(简单搜索)

21级爪哇程序设计新生赛(二)题解_第10张图片
题解:上下左右四个方向搜索如果到达终点就方案+1即可
如果是求最短路,可以用bfs(先搜到的就是最短路)

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int map[10][10], lamp[10][10], startx, starty, endx, endy, n, m, t, total = 0;
int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,-1,0,1 };
void dfs(int x, int y)
{
 	if (x == endx && y == endy)
 	{
  		total++;
  		return;
 	}
 	for (int i = 0; i < 4; i++)
 	{
  		int nowx = x + dx[i];
  		int nowy = y + dy[i];
  		if (nowx > n || nowy > m || nowx == 0 || nowy == 0 || lamp[nowx][nowy] || map[nowx][nowy])
  		{
   			continue;
  		}
  		else
  		{
   			map[nowx][nowy] = 1;
   			dfs(nowx, nowy);
   			map[nowx][nowy] = 0;
  		}
 	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&t);
	scanf("%d%d%d%d",&startx,&starty,&endx,&endy);
 	for (int i = 1; i <= t; i++)
 	{
  		int num1, num2;
  		cin >> num1 >> num2;
  		lamp[num1][num2] = 1;
 	}
 	map[startx][starty] = 1;
 	dfs(startx, starty);
 	printf("%d\n",total);
	return 0;
}

K 经典皇后问题(简单搜索)

21级爪哇程序设计新生赛(二)题解_第11张图片
题解:
n皇后问题
1、首先先将一下每一个数组的含义:boo表示列、dg表示正斜线、udg表示反斜线,每个数组记录的是当前的直线是否已经有一个皇后了(也就是当前位置能不能放)
2、然后讲一下这个dfs是怎么操作的:x表示到第x行,然后枚举当前行里的每一个位置是否能放
21级爪哇程序设计新生赛(二)题解_第12张图片

3、再然后就是这个dgu-i+n中的u-i+n,可以看图设正斜线为y=kx+b。然后代入u=y,k=1,x=i。这样就得出了u=i+b =》 b=u-i。因为i会大于u但是下标要大于等于0,所以我们可以加一个n,也就是b=u-i+n这样就可以防止下标为负(只是将每一个正斜线的位置用唯一的数字来记录,你要是不喜欢,也可以换成别的记录方式,只要是唯一确定的就行,比如说可以把n换成别的数只要最后不为负就行。)
4、udg[u+i]也是同一个道理,y=-kx+b =》b=u+i
5、n皇后挺经典的,如果不能理解,可以自己手动模拟一下

#include
using namespace std;
const int N=10;
int dg[N*2],udg[N*2],boo[N],n;
int total=0;
void dfs(int x)
{
	if(x==n)
	{
		total++;
		return;
	}
	for(int i=0;i<n;++i)
	{
		if(!boo[i]&&!dg[x-i+n]&&!udg[x+i])
		{
			dg[x-i+n]=udg[x+i]=boo[i]=1;
			dfs(x+1);
			dg[x-i+n]=udg[x+i]=boo[i]=0;
		}
	}
}
int main()
{
	scanf("%d",&n);
	dfs(0);
	printf("%d\n",total);
}

L 隔离人数(并查集)

21级爪哇程序设计新生赛(二)题解_第13张图片
题解:将每次聚会的人都合并,记录的方式有两种:
1、res[i]记录的是集合i的人数,每次合并时,人数也合并。
2、最后合并完后,从1-n循环判断谁跟0号是同一个集合的即可
当然这里有个大坑就是聚会的人数可以为0。

#include
using namespace std;
int f[3005],res[3005];
int find(int x)
{
	if (x!=f[x]) f[x]=find(f[x]);
	return f[x];
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=0;i<=n;i++) f[i]=i,res[i]=1;
	while(m--)
	{
		int t;
		scanf("%d",&t);
		int x,y;
		if (t>0)scanf("%d",&x);
		for (int i=1;i<t;i++)
		{
			scanf("%d",&y);
			y=find(y);
			x=find(x);
			if (x!=y) 
			{
				res[x]+=res[y];
				f[y]=x;
			}
		}
	}
	/*
	int ans=0;
	for (int i=0;i<=n;i++) if (find(i)==find(0)) ans++;
	printf("%d\n",ans);
	*/
	printf("%d\n",res[find(0)]);
	return 0;
}

总结

这次的出题相对于第一次新生赛来说可能会简单一点,但总体来说大家做的还是不错的。大家对于并查集和搜索的掌握还是不错的,这次的博弈题并没有什么sg函数、巴什游戏什么的,是想告诉大家博弈是有很多种考察方式的。而动态规划出的题目还是比较简单的(毕竟很多同学说很难学),大家如果没做完或者没做出来的话可以在爪哇oj里的问题,然后选择21新生赛的标签继续完成(注意看题目名是否是21新生赛2开头的,当然要是想把第一次新生赛的题目做完也是可以的)。
在这里插入图片描述

21级爪哇程序设计新生赛(二)题解_第14张图片

这边给出点建议:
语言使用C++,并尽量使用不符合工程代码规范的代码,只追求效率和队友能看懂即可。
如果指针用的不熟,请尽量使用数组,避免莫名其妙的错误。
请使用C++的"普通"语法以及STL,什么C++的类的继承,C++的模板类,命名空间的使用,一切能使得"代码更加长更加好看更加看着舒服"的做法都是在浪费宝贵的5小时上机时间。
加油,希望大家在算法的道路越走越远!!!!!!!

你可能感兴趣的:(算法,c++)