2021年团体程序设计天梯赛赛前模拟赛2(补题)

10 篇文章 1 订阅
8 篇文章 1 订阅

天梯赛座位分配

天梯赛每年有大量参赛队员,要保证同一所学校的所有队员都不能相邻,分配座位就成为一件比较麻烦的事情。为此我们制定如下策略:假设某赛场有 N 所学校参赛,第 i 所学校有 M[i] 支队伍,每队 10 位参赛选手。令每校选手排成一列纵队,第 i+1 队的选手排在第 i 队选手之后。从第 1 所学校开始,各校的第 1 位队员顺次入座,然后是各校的第 2 位队员…… 以此类推。如果最后只剩下 1 所学校的队伍还没有分配座位,则需要安排他们的队员隔位就坐。本题就要求你编写程序,自动为各校生成队员的座位号,从 1 开始编号。

输入格式:
输入在一行中给出参赛的高校数 N (不超过100的正整数);第二行给出 N 个不超过10的正整数,其中第 i 个数对应第 i 所高校的参赛队伍数,数字间以空格分隔。

输出格式:
从第 1 所高校的第 1 支队伍开始,顺次输出队员的座位号。每队占一行,座位号间以 1 个空格分隔,行首尾不得有多余空格。另外,每所高校的第一行按“#X”输出该校的编号X,从 1 开始。

输入样例:
3
3 4 2
输出样例:
#1
1 4 7 10 13 16 19 22 25 28
31 34 37 40 43 46 49 52 55 58
61 63 65 67 69 71 73 75 77 79
#2
2 5 8 11 14 17 20 23 26 29
32 35 38 41 44 47 50 53 56 59
62 64 66 68 70 72 74 76 78 80
82 84 86 88 90 92 94 96 98 100
#3
3 6 9 12 15 18 21 24 27 30
33 36 39 42 45 48 51 54 57 60
思路: 先用mp[x][y]数组把同属一个学校的学生记录下来,记录时,同一学校的人,在数组中是同一排的,也就是x相同,y不一样。
再利用maxx把人数最多的学校的标记。
给学生们编号时,在数组中就是一列一列出阵,也就是说,mp[x][y],外层是y,内层是x,x变化,就是按照学校选不同学校的人,每当所有学校的人都选完时,就回到一开始的学校的第二个接着循环。每个学生的编号就是每次出阵一个学生时,编号就是num,下一个是num+1,当只有一个学校的人时,他们的编号就依次+2。
在这里插入图片描述
举例说明的话,黄色的代表学校,这是一个简易版mp数组,指针j是从上往下移动,i是从左往右移动。从15跳向下一个空间时,发现学校1,没人了,就接着往下移动,当指向18时发现没有其他学校了,就直接编号+2

上述思路是参考了以为大佬的博客
此大佬的博客链接放在下面

https://blog.csdn.net/weixin_37609825/article/details/79797401
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
int mp[105][105],a[105][105];

int main(){
	int n,m,maxx=0;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&m);
		maxx=max(maxx,m);   //选出拥有最多人数的学校 
		for(int j=0;j<10*m;j++){
			mp[i][j]=1;  //一排一排的把每个学校的每个队伍,一排一排,排起来 
		}
	}
	                 
	int num=1,f=-1;     //f来标记上一个人的学校,num是学生们的序号 
	for(int i=0;i<10*maxx;i++){   //maxx是人数最多的学校,以他们为标准,因为下面有if语句判断当前学校的当前位置是否有人,即使某个学校每个这个人时,利用if也会跳过去 
		for(int j=0;j<n;j++){ //因为之前每个学校的人是一排的,一个学校的mp[x][y],x相同,所以每次换学校选人时,都是要变x 
			if(mp[j][i]){     //每次所有学校数n轮完之后,就跳出循环,回到一开始的学校,i++,选下一个人 
				if(f!=j){      //选出来的人与上一个人学校不一样,那么序号就是+1 
					a[j][i]=num++;
					f=j;
				}
				else {             //选出来的人与上一个人学校一样,就要间隔一个数,也就是 
					num+=1;
					a[j][i]=num++;
					f=j;
				}
			}
		}
	}
	
	for(int i=0;i<n;i++){
		printf("#%d\n",i+1);
		for(int j=0;j<10*maxx;j++){
			if(a[i][j]){
				if((j+1)%10==0) printf("%d\n",a[i][j]); //每10个人,来一个换行符 
				else printf("%d ",a[i][j]);
			}
		}
	}
	return 0;
}

至多删三个字符

给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串?

输入格式:
输入在一行中给出全部由小写英文字母组成的、长度在区间 [4, 106 ] 内的字符串。

输出格式:
在一行中输出至多删掉其中 3 个字符后不同字符串的个数。

输入样例:
ababcc
输出样例:
25
提示:

删掉 0 个字符得到 “ababcc”。

删掉 1 个字符得到 “babcc”, “aabcc”, “abbcc”, “abacc” 和 “ababc”。

删掉 2 个字符得到 “abcc”, “bbcc”, “bacc”, “babc”, “aacc”, “aabc”, “abbc”, “abac” 和 “abab”。

删掉 3 个字符得到 “abc”, “bcc”, “acc”, “bbc”, “bac”, “bab”, “aac”, “aab”, “abb” 和 “aba”。
思路: 定义dp[i][j]为前i个字符(1—i),删去j个字符后,得到的不同字符数。
那么对于第i个字符,有两个选择,要么删掉,要么不删掉。
1.不删掉第i个字符,那么dp[i][j]=dp[i-1][j]。因为不删除,所以方案数就是对于前i-1个字符删除j个单字符产生的方案数,也可以想成前i-1个字符删完j个单字符后,再加上一个新的单字符。
2.删掉掉第i个字符,那么dp[i][j]=dp[i-1][j-1],因为要删除,所以方案书就是对于前i-1个字符删除j-1个字符后产生的方案数.
举例说明的话,看abcd这个字符串,len=4,要删掉2个字符。那么假设现在i循环到了4,指向d
如果选择删掉d,那么方案数就相当于abc字符串,删掉1个单字符。
如果选择不删掉,那么方案数就相当于abc字符串,删掉2个单字符,最后形成的字符串就是abc删完两个后,再加上个d。
所以对于前i个字符,删掉j个单字符的方案数,就是上面两者的相加。
因为你无论对第i个单字符选择删不删,对于整体你都是要删掉j个单字符的。删掉与不删掉(反正都是删掉j个)形成的方案数的总和就前i个字符,删掉j个单字符的方案数。
3.上述讨论的是删除以后,不产生重复的情况,也就是每次删去后,产生的都是不同的字符串。但是会有重复情况
下面先展示一个样例
在这里插入图片描述

还要注意多删的问题,当你删除了依次重复删法后,一定要break,一位再往前找相同字符产生的重复情况再删除一遍,重复情况删除一遍就行。
就例如abcdddd,当指向第2个d时,删除1个字符的做法会重复,要删除一次删除第二个‘d’的方案数,当指向第3个d时,删除1个字符时的做法与删除第2个的做法重了,所以删除,删除第三个d的方案数,如果此时不break的话,会再删除一遍删除第二个‘d’的方案数,之前已经删除过了。
4. 最后sum求和,sum=dp[len][0]+dp[len][1]+dp[len][2]+dp[len][3],就是所有删法的总和

下面附上代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long 
const int N=1e6+7;
char s[N];
ll dp[N][5];
int main(){
	scanf("%s",s+1);
	int len=strlen(s+1);
	dp[0][0]=1;
	for(int i=1;i<=len;i++){
		for(int j=0;j<=3;j++){
			if(i<j) break;   //i比j还小,删不了那么多,不够删 
			dp[i][j]=dp[i-1][j];  //这是不删的情况 
			if(j>=1) dp[i][j]+=dp[i-1][j-1];  //j>=1时,这个才成立,1.是为了防止数组越界2.选择了删除,那j一定>=1 
			for(int k=i-1;k>=1&&i-k<=j;k--){  //去重 
				if(s[k]==s[i]){
					dp[i][j]-=dp[k-1][j-(i-k)];
				    break;  
				} 
			}
		}
	}
	ll sum=0;
	for(int i=0;i<=3;i++){
		sum+=dp[len][i];
	}
	printf("%lld\n",sum);
	return 0; 
}

神坛

在古老的迈瑞城,巍然屹立着 n 块神石。长老们商议,选取 3 块神石围成一个神坛。因为神坛的能量强度与它的面积成反比,因此神坛的面积越小越好。特殊地,如果有两块神石坐标相同,或者三块神石共线,神坛的面积为 0.000。

长老们发现这个问题没有那么简单,于是委托你编程解决这个难题。

输入格式:
输入在第一行给出一个正整数 n(3 ≤ n ≤ 5000)。随后 n 行,每行有两个整数,分别表示神石的横坐标、纵坐标(−10
​9
​​ ≤ 横坐标、纵坐标 <10
​9
​​ )。

输出格式:
在一行中输出神坛的最小面积,四舍五入保留 3 位小数。

输入样例:
8
3 4
2 4
1 1
4 1
0 3
3 0
1 3
4 2
输出样例:
0.500
样例解释
输出的数值等于图中红色或紫色框线的三角形的面积。

在这里插入图片描述
题意: 给一些点,让你求组成三角形的最小面积。
思路: 一共n个点,对于每个点我们都可以把它作为顶点,那么此点与其他店就可以形成n-1个向量。
n-1个向量中,我们任意选取两个向量都可以满足“选取三块石头”这个条件,但是我们现在的目标是面积最小,因此我们需要对这n-1个向量进行极角排序。
(关于极角排序还请看别的大佬的讲解,这里就不过多赘述了,主要还是我对这种排序了解的不透彻
三角形的面积用叉乘式来求。
注意: 一开始输入的时候要用long long,而不能用double,否则会有两个测试点过不了。

时间复杂度大约O(n2logn)

#include<iostream>
#include<cstring>
#include<algorithm>
#include<math.h>
using namespace std;
#define ll long long
const int N=5050;
struct node{
	ll x,y;
}a[N],t[N];
bool cmp(node a,node b){
	return a.x*b.y>a.y*b.x;
} 
int main(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%lld%lld",&a[i].x,&a[i].y);
	}
	ll ans=2e18;
	for(int i=0;i<n;i++){
		int k=0;
		for(int j=0;j<n;j++){
			if(i==j) continue;
			t[k].x=a[j].x-a[i].x;
			t[k++].y=a[j].y-a[i].y;		
		}
		sort(t,t+k,cmp);  //极角排序
		for(int j=0;j<k;j++){
			ans=min(ans,abs(t[j].x*t[(j+1)%k].y-t[j].y*t[(j+1)%k].x)); //叉乘式求面积
		}
	}
	printf("%.3lf\n",ans*0.5);
	return 0;
}

小字辈

本题给定一个庞大家族的家谱,要请你给出最小一辈的名单。

输入格式:
输入在第一行给出家族人口总数 N(不超过 100 000 的正整数) —— 简单起见,我们把家族成员从 1 到 N 编号。随后第二行给出 N 个编号,其中第 i 个编号对应第 i 位成员的父/母。家谱中辈分最高的老祖宗对应的父/母编号为 -1。一行中的数字间以空格分隔。

输出格式:
首先输出最小的辈分(老祖宗的辈分为 1,以下逐级递增)。然后在第二行按递增顺序输出辈分最小的成员的编号。编号间以一个空格分隔,行首尾不得有多余空格。

输入样例:
9
2 6 5 5 -1 5 6 4 7
输出样例:
4
1 9
题意: 就是从1—n,告诉你每个人的上一辈的编号是谁,让你输出最小辈分,并把最小辈分的人输出。(辈分越大,数字越大,老祖宗的辈分为1)
思路: 利用fa[]数组求出每个人的父辈是谁,再用rnk[]数组记录每个人的辈分 (rank用不了,会编译错误),用max找出最小辈分,即数字最大,在把是这个辈分的人输出。
思想类似并查集,把有关系的人都连起来。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100005;
int fa[N],rnk[N];
int find(int x){  //求x的辈分 
	if(fa[x]==-1) return rnk[x]=1;
	if(rnk[x]==0) return rnk[x]=find(fa[x])+1;
	else return rnk[x];
}
int main(){
	int n,maxx=-1;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&fa[i]); //数字i的父亲是谁 
	} 
	for(int i=1;i<=n;i++){
		maxx=max(maxx,find(i)); //最小辈分 
	}
	printf("%d\n",maxx);
	for(int i=1,j=0;i<=n;i++){
		if(rnk[i]==maxx){
			if(j==1) printf(" %d",i);  //这里是为了输出格式正确 
			else printf("%d",i);
			j=1;
		}
	}
	printf("\n");
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值