2019级第二次月赛暨软件计科联合新生赛题解

2019级第二次月赛暨软件计科联合新生赛题解

  • 题目难度:

较难题: E , J E,J
A K AK 题: I I
其余题目均为一般可做题

  • 题解:

A.帮助kiki

题意:

给定一个字符串,连续相邻3个字符相等的可以消去,重复此操作,要求输出操作次数和最后剩下的字符串。

题解:

栈的原理(类似于括号配对问题),数组模拟,把字符依次放入栈,当栈的大小大于等于3的时候随后放进来的跟前面两个比较相等弹出栈并记录答案,最后输出栈里还剩的字符即可(特判栈为空的情况)。

标程:

#include<cstdio>
#include<cstring>
const int N = 1e5+5;
char st_Q[N],s[N];
int main() {
	while(~scanf("%s",s)) {
		int ans=0,top=0;
		int len =strlen(s);
		for(int i=0; i<len; i++) {
			st_Q[top++]=s[i];//入栈
			if(top>=3&&st_Q[top-1]==st_Q[top-2]&&st_Q[top-2]==st_Q[top-3]) {
				top-=3;//相当于出栈
				ans++;
			}
		}
		printf("%d ",ans);
		if(top) {
			for(int i=0; i<top; i++)printf("%c",st_Q[i]);
		} else {
			printf("qwq");
		}
		printf("\n");
	}
	return 0;
}

B.kuku吃糖果

题意:

给定种类和对应数量的糖果,现在不能两次吃同一种糖果,问你是否存在一种吃糖果的顺序把所有糖果都吃完。

题解:

高中数学组合原理,插空法:考虑最大数量的那种糖果,如果剩下的糖果能插在这些数量中间即可,否则不行。(注意和会暴int,虽然不影响结果

标程:

#include<cstdio>
#define max(a,b) a>b?a:b
int main() {
	int t;
	while(~scanf("%d",&t)) {
		while(t--) {
			int n,maxn=0;
			long long sum=0;
			scanf("%d",&n);
			while(n--) {
				int x;
				scanf("%d",&x);
				maxn=max(x,maxn);
				sum+=x;
			}
			if(sum-maxn>=maxn-1) {//剩下的数量为sum-maxn,最大数量的这种糖果有maxn-1个空
				printf("Yes\n");
			} else {
				printf("No\n");
			}
		}
	}
	return 0;
}

C.kuku会读数

题意:

给定 7 21 7*21 的字符图案,打印读数结果。

题解:

这类题,观察不同类型数字的特征为最好办法。(特征有很多,代码仅供参考)

参考代码;

#include<iostream>
#include<cstdio>
using namespace std;
char a[7][21];
int solve(int id) {
	if (a[0][id + 1] == 'X') {
		if (a[1][id] == 'X') {
			if (a[1][id + 3] == 'X') {
				if (a[3][id + 1] == 'X') {
					if (a[4][id] == 'X')
						return 8;
					else
						return 9;
				} else {
					return 0;
				}
			} else {
				if (a[4][id] == 'X')
					return 6;
				else
					return 5;
			}
		} else {
			if (a[4][id] == 'X')
				return 2;
			else if (a[3][id+1] == 'X')
				return 3;
			else
				return 7;
		}
	} else {
		if (a[1][id] == 'X')
			return 4;
		else
			return 1;
	}
}
int main() {
	int t;
	scanf("%d",&t);
	while(t--) {
		for(int i=0; i<7; i++) {
			scanf("%s",a[i]);
		}
		int a,b,c,d;
		a=solve(0);//这里用到solve(id)函数 ,表示从id列开始的图形区域,来区分四个数字 
		b=solve(5);
		c=solve(12);
		d=solve(17);
		printf("%d%d:%d%d\n",a,b,c,d);
	}
	return 0;
}

D.kuku的电话号码

题意:

给定一个数字串,求能组成多少个由 8 8 开头的 11 11 位电话号码。(每一个位置的数字只能用一次,组成的电话号码可以一样)

题解:

输入字符串后经过计算 k k :数字8的个数 , l e n len :数字的总个数,那么: 如果 k < l e n 11 k<\frac{len}{11} 最多组成 k k 个电话号码,否则最多组成 l e n 11 \frac{len}{11} 个号码,所以答案是 m i n ( k , l e n 11 ) min(k,\frac{len}{11})

标程:

#include<cstdio>
#include<cstring>
#define min(a,b)  a<b?a:b
const int N = 2005;
char s[N];
int main() {
	int len;
	int k;//记录字符8出现的次数
	while(~scanf("%d",&len)) {
		k=0;
		scanf("%s",s);
		for(int i=0; i<len; ++i) if(s[i]=='8') k++;
		int ans=min(k,len/11);
		printf("%d\n",ans);
	}
	return 0;
}

E.kuku的无敌序列

题意:

给定 n n 个三元组,还原原序列,如果原序列不唯一,输出字典序最小的序列。

题解:

思路1:

d f s dfs 搜索:本题会发现在 n 10 n≤10 的时候可以进行 d f s dfs 的全排列搜索,初设数列设为 1 n 1-n ,去判断此时数列的所有三元组是否与输入的完全一致,如果一致,输出当前数列,不一致时进行字典序的升序操作,再进行判断,一定会有一个数列满足输入情况。

思路2:

当一个数列的所有三元组给出后,可以发现,数列的第一个位置和最后一个位置,一定只出现了一次,第二个位置和倒数第二个位置出现了两次,其余位置出现了三次,可以先进行桶排计次后,寻找1-n中第一个只出现一次的数,将那个数元组中所有数的计次减一,同一元组中下一个次数为一的位置即为下一个数

如:元组为(1,4,2),(4,2,3),(2,3,5)时

1共计一次,2三次,3两次,4两次,5一次

所以首位置为1,将第一个元组中所有计次减一,4的计次变为1,2的计次变为2;

得到第二个位置的数是4,第三个位置的数是2,然后寻找与本元组仅一个数不同的元组,可得到下一个数为3,然后可得到再下一个数为5

所以数列为1 4 2 3 5

标程:

#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;

vector<int> g[15];
int vis[15];

int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n-2; i++) {
		int x, y, z;
		cin >> x >> y >> z;
		vis[x]++;
		vis[y]++;
		vis[z]++;
		g[x].push_back(y);
		g[x].push_back(z);
		g[y].push_back(x);
		g[y].push_back(z);
		g[z].push_back(x);
		g[z].push_back(y);
	}
	if(n == 3) {              //对于n = 3 和 4 的时候,元组中不存在计次为3的数,进行直接判断输出即可
		printf("1 2 3\n");
		return 0;
	} else if(n == 4) {
		int t;
		for (int i = 1; i <= n; i++) {
			if (vis[i] == 1) {
				t = i;
				printf("%d ",i);
				break;
			}
		}
		for (int i = 1; i <= n; i++) {
			if (vis[i] == 2)
				printf("%d ",i);
		}
		for (int i = n; i >= 1; i--) {
			if (vis[i] == 1) {
				t = i;
				printf("%d ",i);
				break;
			}
		}
		return 0;
	}
	int s1 = 0, s2 = 0;
	for (int i = 1; i <= n; i++) {      //找到第一个位置的数
		if (vis[i] == 1) {
			s1 = i;
			break;
		}
	}
	for (int i = 1; i <= n; i++) {     //找到第二个位置的数
		if (vis[i] == 2) {
			int ok = 0;
			for (int to : g[s1]) {
				if (to == i)
					ok = 1;
				if (ok)
					s2 = i;
			}
		}
	}
	vis[s1] = vis[s2] = -1;       //标记为-1代表用过这个数了
	printf("%d %d", s1, s2);
	for (int i = 3; i <= n; i++) {   //由于三元组可知,已知两个位置可寻找下一位置,从第三个位置开始依次寻找下一个位置的数
		int nt = 0;
		for (int to : g[s1]) {
			if (vis[to] != -1)
				nt = to;
		}
		s1 = s2;
		s2 = nt;
		vis[nt] = -1;
		printf(" %d", nt);
	}
	return 0;
}

ps:看不懂的问出题人 w b t q q 757481375 wbt:qq:757481375 ,或者学习别人的暴力代码。

F.kuku的生命线

题意:

输入一个数,根据其模4所得数字大小来判定等级。

题解:

此题是一道数学题,为了在限定范围内达到最高等级,先求出n被4除的余数,然后根据余数判断即可。

标程:

#include <cstdio>
int main() {
	int n;
	scanf("%d", &n);
	n %= 4;
	if (n == 1) {
		printf("0 A");
	} else if (n == 2) {
		printf("1 B");
	} else if (n == 3) {
		printf("2 A");
	} else {
		printf("1 A");
	}
	return 0;
}

G.kuku的冠军梦想

题意:

题数大的为冠军,比较字符串即可。

题解:

比较字符串长度即可,字符串长度相同时,比较字典序。

标程1:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
struct node{
	int num;
	string a;
}st[25];
int cmp(node xx,node yy){
	if(xx.a.length()!=yy.a.length()){
		return xx.a.length()<yy.a.length();
	}else{
		return xx.a<yy.a;
	}
}
int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		st[i].num=i+1;
		cin>>st[i].a;
	}
	sort(st,st+n,cmp);
	cout<<st[n-1].num<<endl<<st[n-1].a;
	return 0;
}

标程2:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
char ans[105],b[105];
int main() {
	int n,len,max=0,num;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%s",b);
		len=strlen(b);
		if(len>max) {
			max=len;
			num=i;
			strcpy(ans,b);
		} else if(len==max) {
			if(strcmp(ans,b)<0) {
				num=i;
				strcpy(ans,b);
			}
		}
	}
	printf("%d\n%s\n",num,ans);
	return 0;
}

H.幸运的字符串

题意:

判断给定的字符串是否幸运:如果字符串包含的所有字母都是连续(相邻)的字母,并且每个字母恰好出现一次,则该字符串称为幸运的字符串。

题解:

桶记录字母出现次数, m i n n minn 记录最小字母, m a x x maxx 记录最大字母,然后从 m i n n minn m a x x maxx 遍历桶,存在非1就不幸运,否则幸运。(当然sort再判断也是可以的)。

参考代码:

#include<cstdio>
#include<cstring>
#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b
#define N 1005
int ans[N];
char s[N];
int main() {
	int n;
	scanf("%d",&n);
	while(n--) {
		memset(ans,0,sizeof(ans));
		scanf("%s",s);
		int maxx = 0,minn ='z'-'a';
		int len =strlen(s);
		for(int i=0; i<len; i++) {
			ans[s[i]-'a']++;
			maxx = max(maxx,s[i]-'a');
			minn = min(minn,s[i]-'a');
		}
		bool flag =0;
		for(int i=minn; i<=maxx; i++) {
			if(ans[i] != 1) {
				flag =1;
				break;
			}
		}
		printf("%s\n",flag?"Unlucky":"Lucky");
	}
	return 0;
}

I.定西

题意:

给定一个 n m n*m 迷宫单元,有一个无限区域由这个迷宫单元平铺,给定一个起点,问能否无限走下去。

bfs题解:

事实上,只要从一个地图块上的某一点 ( x ,   y ) (x,\ y) 出发走到其他地图块上的同一点 ( x ,   y ) (x,\ y) 处,即可无限前进。把这一点想清楚以后问题就解决了。

注意,上述的点 ( x ,   y ) (x,\ y) 不一定必须是边界点。

至于如何记录是从不同地图到达的,需要一些小技巧。我们在 B F S BFS 时维护无限大地图上的绝对坐标 ( a x ,   a y ) (ax,\ ay) ,另外维护一个数组 f r o m [ r x ] [ r y ] from[rx][ry] 表示地图块上的某一相对坐标 ( r x ,   r y ) (rx,\ ry) 所对应的绝对坐标,如果出现 f r o m [ r x ] [ r y ] from[rx][ry] 已有记录、且与当前 ( a x ,   a y ) (ax,\ ay) 不同的情况,则说明我们到达了不同地图块上的同一点。

ps:在 n u o y a n l i O j nuoyanliOj (栈深度1e7)本题用DFS也可以AC,那就是因为 OJ 上的栈空间很大(比如 N S W O J 2.0 NSWOJ2.0 或者 D o m j u d g e Domjudge ),在通常的OJ或者 PC^2 上递归这么多层早就爆掉了。因此建议用 B F S BFS 求解。

bfs标程:

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
struct Point {
	int x, y;
	Point(int i, int j) : x(i), y(j) {}
	Point() {}
};

const int dx[] = { 1, -1, 0, 0 };
const int dy[] = { 0, 0, 1, -1 };
const int MAX = 2005;
char maze[MAX][MAX];
Point from[MAX][MAX];
int n, m;

bool bfs(Point st) {
	queue<Point> q;
	q.push(st);
	while (!q.empty()) {
		Point p = q.front();
		q.pop();
		for (int i = 0; i < 4; i++) {
			int ax = p.x + dx[i], ay = p.y + dy[i];
			int rx = ax, ry = ay;
			rx = ((rx % n) + n) % n;//如果超过n*m边界就到下一个单元的对应位置
			ry = ((ry % m) + m) % m;
			if (maze[rx][ry] == '#') {
				continue;
			}
			if (from[rx][ry].x == -1&&from[rx][ry].y==-1) {
				from[rx][ry] = Point(ax, ay);
				q.push(Point(ax, ay));
			} else if (from[rx][ry].x != ax||from[rx][ry].y != ay) {
				return true;
			}
		}
	}
	return false;
}

int main() {
	// freopen("test.txt", "r", stdin);
	while (scanf("%d %d", &n, &m) != EOF) {
		memset(from, -1, sizeof(from));
		for (int i = 0; i < n; i++) {
			scanf("%s", maze[i]);
		}
		Point st;
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++) {
				if (maze[i][j] == 'S') {
					st = Point(i, j);
				}
			}
		bool flag = bfs(st);
		if (flag) {
			printf("Yes\n");
		} else {
			printf("No\n");
		}
	}
	return 0;
}

dfs题解:

能走到起点无限远就意味着可以从起始点沿着某个方向一直走,而不会走回头路回到起点。(进入循环)因此我们dfs要用到4个参数: x y f x f y x,y,fx,fy ,其中 x y x、y 代表此时在地图上的位置,也即当 x < 0 x > = n y < 0 y > = m x<0||x>=n||y<0||y>=m 的时候是要对 x x y y 进行单元格操作的,但是 f x fx f y fy 代表当前位置相对初始位置的移动,无需做额外操作,那么当 f x fx == x x &&$ f y = = y fy==y 且该点被访问过的时候,说明这是一个死循环,但不是解。因为沿着某个方向若能回到起点, 必定是满足 f x ! = x f y ! = y fx!=x||fy!=y 的。 那么我们考虑用一个三维 v i s vis 数组,每次 d f s dfs 令: v i s [ x ] [ y ] [ 1 ] = f x v i s [ x ] [ y ] [ 2 ] = f y v i s [ x ] [ y ] [ 0 ] = 1 vis[x][y][1]=fx,vis[x][y][2]=fy,vis[x][y][0]=1 ,那么当 v i s [ x ] [ y ] [ 0 ] vis[x][y][0] && ( v i s [ x ] [ y ] [ 1 ] ! = x v i s [ x ] [ y ] [ 2 ] ! = y ) (vis[x][y][1]!=x||vis[x][y][2]!=y) 的时候,说明我们找到了一个满足题意的解。即经过有效的移动又走到了以前走过的点,那么必定可以沿着这条路径一直走下去。若 v i s [ x ] [ y ] [ 0 ] = 1 vis[x][y][0]=1 但是不满足上式,说明已经走过。

dfs标程:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int vis[2005][2005][3];//:vis[x][y][1]=fx,vis[x][y][2]=fy,vis[x][y][0]=1,那么当vis[x][y][0]&&(vis[x][y][1]!=x||vis[x][y][2]!=y)的时候,说明我们找到了一个满足题意的解
char s[2005][2005];
int n,m;
bool flag;
const int dir[4][2]= {{-1,0},{1,0},{0,-1},{0,1}};

void dfs(int x,int y,int fx,int fy) {
	if(flag) {
		return;
	}
	if(vis[x][y][0]&&(vis[x][y][1]!=fx||vis[x][y][2]!=fy)) { //重复走过的点
		flag=true;
		return;
	} else if(vis[x][y][0]) {
		return ;
	}
	vis[x][y][0]=1;
	vis[x][y][1]=fx;
	vis[x][y][2]=fy;
	for(int i=0; i<4; i++) {
		int dx=(x+dir[i][0]+n)%n;
		int dy=(y+dir[i][1]+m)%m;
		if(s[dx][dy]!='#')
			dfs(dx,dy,fx+dir[i][0],fy+dir[i][1]);
	}
}

int main() {
	while(~scanf("%d %d",&n,&m)) {
		flag=0;
		memset(vis,0,sizeof(vis));
		int x=0,y=0;
		for(int i=0; i<n; i++) {
			for(int j=0; j<m; j++) {
				cin>>s[i][j];
				if(s[i][j]=='S')
					x=i,y=j;
			}
		}
		if(n==1&&m==1) {
			printf("Yes\n");
			continue;
		}
		dfs(x,y,x,y);
		printf("%s\n",flag?"Yes":"No");
	}
	return 0;
}

J.kuku的排列组合

题意:

求组合数 C ( m n ) C\binom{m}{n} ,其中: C ( m n ) = n ! m ! ( n m ) ! C\binom{m}{n}=\frac{n!}{m!*(n-m)!} 1 m n 61 1 \leq m \leq n \leq 61

题解:

不难发现单纯暴力求解会导致中间结果溢出(不展开公式你就错了\[斜眼笑]​),所以一定要在计算过程中约分。

比如

C ( 4 9 ) = 6 7 8 9 1 2 3 4 C\binom{4}{9}=\frac{6*7*8*9}{1*2*3*4} ,先计算 6 / 1 6/1 再乘上 7 / 2 7/2 再乘上 8 / 3 8/3 再乘上 9 / 4 9/4

6 / 1 = C ( 1 6 ) 6/1=C\binom{1}{6}
( 6 7 ) / ( 1 2 ) = C ( 2 7 ) (6*7)/(1*2)=C\binom{2}{7}
( 6 7 8 ) / ( 1 2 3 ) = C ( 3 8 ) (6*7*8)/(1*2*3)=C\binom{3}{8}
… 一定都是整数,即都能整除

标程1:

#include<cstdio>
typedef long long ll;
ll C(ll n,ll m) {
	ll ans=1;
	for(ll i=1; i<=m; i++) {
		ans=ans*(n-m+i)/i;
	}
	return ans;
}
int main() {
	int n,m;
	while(~scanf("%d%d",&n,&m)) {
		printf("%lld\n",C(n,m));
	}
	return 0;
}

当然这里还有一个可以小优化的(在这里优化与否无所谓): C ( m n ) = C ( n m n ) C\binom{m}{n}=C\binom{n-m}{n}

标程2:

#include<cstdio>
typedef long long ll;
ll C(ll n,ll m) {
	ll ans=1;
	for(ll i=1; i<=m; i++) {
		ans=ans*(n-m+i)/i;
	}
	return ans;
}
int main() {
	int n,m;
	while(~scanf("%d%d",&n,&m)) {
		if(n-m<m)m=n-m;
		printf("%lld\n",C(n,m));
	}
	return 0;
}

还有这个公式: C ( m n ) = C ( m 1 n 1 ) + C ( m n 1 ) C\binom{m}{n}=C\binom{m-1}{n-1}+C\binom{m}{n-1} (杨辉三角),那么就存在另外一种递推预处理:
在这里插入图片描述

参考代码:

#include<cstdio>
typedef long long ll;
const int N=62;
ll ans[N][N];
void init_C() {
	for(int i=0; i<N; i++) {
		ans[i][0]=ans[i][i]=1;//初始化边界
	}
	//回想一下杨辉三角理解
	for(int i=2; i<N; i++) { //00 11 10 都算出来
		for(int j=0; j<=i/2; j++) {
			ans[i][j]=ans[i-1][j]+ans[i-1][j-1];
			ans[i][i-j]=ans[i][j];//另外半部分直接算出来
		}
	}
}
int main() {
	int n,m;
	init_C();//一次求出所有结果
	while(~scanf("%d%d",&n,&m)) {
		printf("%lld\n",ans[n][m]);//输出即可
	}
	return 0;
}
  • 想说的话:

比赛的时候发现很多同学因为数组越界或者没有初始化错误(比如H题),下次就需要注意一点了,因为有时候你的编译器不会报错误,即便是越界或者没有初始化的时候自己运行也是对的,而交上去就错,这个时候需要做的就是定义全局变量/注意初始化

发布了293 篇原创文章 · 获赞 212 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/nuoyanli/article/details/103338997
今日推荐