第五届“传智杯”全国大学生计算机大赛(练习赛)

前言:这次练习赛对应的洛谷原题分别是B3654、P8547、P8444、P8462、P8827、P5391。有需要的可以去洛谷找原题看题解弄懂自己不会的

官方给的:练习赛满分程序(多语言):云剪贴板 - 洛谷

目录

复读

题目描述

输入格式

输出格式

输入输出样例

说明/提示

时钟 

题目描述

输入格式

输出格式

输入输出样例

说明/提示

 平等的交易

输入格式

输出格式

输入输出样例

说明/提示

清洁工 

题目描述

输入格式

输出格式

输入输出样例

说明/提示

树的变迁 

题目描述

输入格式

输出格式

输入输出样例

说明/提示

白色旅人 

题目描述

输入格式

输出格式

输入输出样例

说明/提示

样例解释

数据范围及约定


复读

普及-的难度,签到题了属于是。 

 T292219 [传智杯 #5 练习赛] 复读 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

给定若干个字符串,不定数量,每行一个。有些字符串可能出现了多次。如果读入一个字符串后,发现这个字符串以前被读入过,则这个字符串被称为前面相同的字符串的复读,这个字符串被称为复读字符串。相应的,每个首次出现的字符串就是非复读字符串

举个例子,

abc
def
abc
abc
abc

第 1,3,4,5行是字符串 abc,那么 3,4,5行的字符串会被称为“复读”。

请你把所有的非复读字符串,按照行号从小到大的顺序,依次拼接为一个长串并输出。

输入格式

多个字符串,每行一个,含义见题目描述。

注意:如果这个字符串是 0,说明所有字符串都读完了。这个 0 不认为是一个“非复读字符串”。

输出格式

共一行,表示所有非复读字符串,按照行号从小到大依次连接的结果。

输入输出样例

输入 #1

cc
b
a
cc
0

输出 #1

ccba

说明/提示

【数据范围】

字符串的个数不超过 500个,字符串总长度不超过 50000,每个字符串中只包含小写字母、数字、 . 、! 和 &,不包含空格等特殊符号。

思路:每次输入一行字符串,判断是否为0,如果是0则跳出循环,否则判断该字符串是否出现过,如果没有出现过就输入该字符串并将该字符串记录下来。

#include<bits/stdc++.h>
#define ll long long  
#define f(i,j,n) for(int i=j;i<n;i++)
using namespace std;
vector<string>v; 
int main () {
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	string s;
	while(cin>>s){
		if(s=="0")break;
		int x=1;
		for(auto i:v){
			if(i==s){
				x=0;
				break;
			}
		}
		if(x){
			cout<<s;
			v.push_back(s);
		}
	}
	return 0;
}

时钟 

普及-的难度,思路也比较简单,但是由于自己赛时算错了一天有多少分钟导致吃了罚时。 

T292112 [传智杯 #5 练习赛] 时钟 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

你有一个电子钟,可以显示 0:00 到 23:59 之间的所有时间,以数字的形式显示。其中小时是 0 到 23(0 时会显示一个 0,而 1 到 9 时不会显示前导 0),分钟是 00 到 59(0 到 9 分都会显示前导 0)。任何时刻,电子钟都会显示三个或者四个 0 到 9 的数字。如果在某时刻,这些数字依次组成了一个等差数列,则这个时刻被称为“好时刻”。

你感觉很无聊,从 0:00 时刻开始盯着这个电子钟。一共盯了 xx 分钟。请问整个过程中,"好时刻"来临了多少次(算上开头和结尾)?

输入格式

一个不超过 109 的非负整数。

输出格式

请输出"好时刻"来临了多少次?

输入输出样例

输入 #1

120

输出 #1

10

输入 #2

2880

输出 #2

79

输入 #3

987654321

输出 #3

26748975

说明/提示

【样例解释】

你观察了 2 个小时,其中这些“好时刻”来临了:

0:00
0:12
0:24
0:36
0:48
1:11
1:23
1:35
1:47
1:59

一共是 10 个。

思路:首先打表知道一圈有多少个,然后输入n,如果n少于一圈,直接输出;如果大于1圈,判断一共走了多少圈然后加上剩余分钟内的“好时刻”即为结果。

#include<bits/stdc++.h>
#define ll long long  
#define f(i,j,n) for(int i=j;i<n;i++)
using namespace std;
int b[2500],s[1450];
int main () {
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	memset(b,0,sizeof(b));
	for(int i=0;i<24;i++){
		for(int j=0;j<6;j++){
			for(int k=0;k<10;k++){
				if(i+k==2*j&&i<10){
					b[i*60+j*10+k]=1;
				}
				else if(i>9&&i%10-i/10==j-i%10&&j-i%10==k-j){
					b[i*60+j*10+k]=1;				
				}
			}
		}
	}
	for(int i=1;i<1450;i++){
		b[i]+=b[i-1];
	}
	long long int n,q;
	cin>>n;
	if(n<1440)cout<<b[n];
	else {
		q=n/1440*39;
		n-=n/1440*1440;
		q+=b[n];
		cout<<q;
	}
	return 0;
}

 平等的交易

普及-的难度,还是可以写的简单题,赛时把打破条件弄错了影响结果。 

T292113 [传智杯 #5 练习赛] 平等的交易 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

你有 n 道具可以买,其中第 i 的价格为ai​。

你有 w 元钱。你仅能用钱购买其中的一件商道具。当然,你可以拿你手中的道具换取其他的道具,只是这些商道具的价值之和,不能超过你打算交换出去的道具。你可以交换无数多次道具。道具的价值可能是 0,但是你不能使用空集换取价值为 0 的商品。

请问,在这个条件下,最多可以换取多少件道具?

输入格式

第一行一个正整数 n,表示道具个数。

接下来一行 n 个正整数,表示{an​}。

接下来一行 1 个正整数,表示 w。

输出格式

一个正整数,表示答案。

输入输出样例

输入 #1复制

3 
1 1 2
5

输出 #1复制

2

说明/提示

【样例解释】

买价值为 2 的道具,并交换为两个价值为 1 的道具。

【数据范围及约束】

测试数据满足,1≤n≤1e6,0≤ai​≤1e9,1≤2*1e9

 首先从小到大排序,从后往前找到可以使用的最大的小于或者等于w的值,然后从小到大看可以找出多少个满足条件的可交换物品。(下面贴上两种AC代码,赛时老是WA是因为写错了第二层循环的打破条件)

#include<bits/stdc++.h>
#define ll long long  
#define f(i,j,n) for(int i=j;i<n;i++)
using namespace std;
int n,q;
int a[1000005];
ll w;
int main () {
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	f(i,0,n)cin>>a[i];
	cin>>w;
	sort(a,a+n);
	if(w>=a[0]){
		for(int i=n-1;i>=0;i--){
			if(w>=a[i]){
				w=a[i];
				break;
			}
		}
		for(int i=0;i<n;i++){
			if(w>=a[i]){
				w-=a[i];
				q++;
			}
		}
	}
	cout<<q;
	return 0;
}
#include<bits/stdc++.h>
#define ll long long  
#define f(i,j,n) for(int i=j;i<n;i++)
using namespace std;
int n,q;
int a[1000005];
ll w,sum;
int main () {
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	f(i,0,n)cin>>a[i];
	cin>>w;
	sort(a,a+n);
	if(w>=a[0]){
		for(int i=n-1;i>=0;i--){
			if(w>=a[i]){
				w=i;
				break;
			}
		}
		for(int i=0;i<n;i++){
			sum+=a[i];
			if(sum>a[w]){
				q=i;
				break;
			}
		}
	}
	cout<<q;
	return 0;
}

清洁工 

普及-的难度,思路还是很好想的,就是在最后的时候注意一下输出的格式就行 

T292114 [传智杯 #5 练习赛] 清洁工 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

有一个 n×n 的地块,一个连续 i 分钟没人经过的地面在第 i 分钟会落上 i个单位的灰,有人经过时不会落灰但灰也不会清零,在人走后第一分钟又会落上一个单位的灰,以此类推。你在这个n×n 的范围内移动,你的移动轨迹可以描述为一个由N,S,W,E 组成的字符串,每个字母分别表示上、下、左、右。这个人一开始在点 (x,y),每一分钟移动一步。

求最后每一个位置上落下的灰的量。

本题中的上和右分别表示 y 轴正方向和 x 轴正方向。保证你没有超过移动的范围。

输入格式

第一行四个正整数n,m,x,y,含义如题面所示,其中 x,y 表示横纵坐标,不是数组下标。
第二行一个长度为m 的字符串,表示你的移动序列。

输出格式

共n 行,每行 n 个数,第 i 行的第 j 个数表示坐标(j,n−i+1) 上的灰的数量

输入输出样例

输入 #1复制

5 4 1 1
NENW

输出 #1复制

10 10 10 10 10 
10 10 10 10 10 
10 6 10 10 10 
4 4 10 10 10 
6 10 10 10 10 

输入 #2复制

7 14 1 1
NENENENENESSSS

输出 #2复制

105 105 105 105 105 105 105 
105 105 105 105 55 61 105 
105 105 105 49 51 69 105 
105 105 51 49 105 79 105 
105 61 55 105 105 91 105 
79 69 105 105 105 105 105 
91 105 105 105 105 105 105 

输入 #3复制

10 70 2 2
NWSNSNNNSNNSSNNSENNNNEESNWSESESSWENNSEWESWWWESEEESENNSENWNESNWSNNNEESS

输出 #3复制

2485 2485 2485 2485 2485 2485 2485 2485 2485 2485 
2485 1407 1205 1267 2485 2485 2485 2485 2485 2485 
2485 1435 1281 1167 2485 2485 2485 2217 2281 2347 
2485 1465 2485 1255 1041 2485 2485 2155 2485 2415 
1557 1497 2485 2485 969 1177 2485 1733 1807 2485 
1471 1531 1315 907 935 1267 2485 1473 1647 2485 
1631 2485 2485 1357 1381 1407 1435 1499 1645 2485 
2021 2347 2485 2485 2485 2485 1465 1497 2485 2485 
2087 2415 2485 2485 2485 2485 2485 2485 2485 2485 
2485 2485 2485 2485 2485 2485 2485 2485 2485 2485 

输入 #4复制

5 4 2 1
NENW

输出 #4复制

10 10 10 10 10 
10 10 10 10 10 
10 10 6 10 10 
10 4 4 10 10 
10 6 10 10 10 

说明/提示

本题 y 轴朝上,x 轴朝右,样例输出中的左下角表示(1,1),第一分钟你在初始点处,第二分钟移动到相应的位置,第 m+1 分钟移动到最后一个点,但是总共只有 m 分钟,因此最后一个点不受移动的影响


样例 1 解释:

你的移动路径为(1,1)→(1,2)→(2,2)→(2,3)→(1,3),共 4 分钟。

对于第 1 分钟,(1,1) 灰层数不变,其余点被落下了 1 层灰。

对于第 2 分钟,(1,2) 灰层数不变,(1,1) 被落下了 1 层灰,其余点落下 2 层灰。

对于第 3 分钟,(2,2) 灰层数不变,(1,1) 落下 2 层灰,(1,2) 落下 1 层灰,其余点落下 3 层灰。

对于第 4 分钟,(2,3) 灰层数不变,(1,1) 落下 3 层灰,(1,2) 落下 2 层灰,(2,2) 落下 1层灰,其余点落下 4 层灰。

注意最后你移动到了 (1,3),但是时间只有 4 分钟,所以实际上不会对 (1,3) 造成影响。初始点不一定在 (1,1)。

1≤n≤50,1≤m≤1000。

思路:由于题目给的数据范围比较小,直接暴力即可。只要坐标不是被走到的那一个,灰尘就可以直接加1, 如果是被走到的点,灰尘数不变并将记录的灰尘累计单位清零。最后按照题目要求的格式输出每个坐标的灰尘数即可。  

#include<bits/stdc++.h>
#define ll long long  
#define f(i,j,n) for(int i=j;i<n;i++)
using namespace std;
int n,m,x,y;
int a[55][55];
int b[55][55];
string s;
int main () {
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m>>x>>y>>s;
	for(int k=0;k<m;k++){
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++){
					if(i!=x||j!=y){
						b[i][j]++;
						a[i][j]+=b[i][j];
					}
					else{
						b[i][j]=0;
						a[i][j]+=b[i][j];
					}
				}
			}
		if(s[k]=='N'){
			y++;
		}
		else if(s[k]=='S'){
			y--;			
		}
		else if(s[k]=='W'){
			x--;
		}
		else{
			x++;
		}
	}
	for(int i=n;i>0;i--){
		for(int j=1;j<=n;j++){
			cout<<a[j][i]<<' ';
		}cout<<"\n";
	}
	return 0;
}

树的变迁 

普及+/提高,直接就是绿题了,考察树的东西我这种小菜鸡已经搞不定了。 

T292115 [传智杯 #5 练习赛] 树的变迁 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

给定一棵具有 n 个节点的树,每个节点有一个初始权值 ai​。

一共需要进行 m 次操作,每次操作包括:

  • 1 e 编号为 e 的边突然消失,使得它所在的那棵树变成了两棵树。

  • 2 u val 编号为 u 的节点的权值变成了val。

  • 3 u 进行了一次查询,查询 u 所在的那棵树的权值之和。

现在你需要来模拟上述事件,以了解树的变迁。

输入格式

第一行为n,m,如上所述。

第二行有 n 个数,为 n 个结点的初始权值,在 10^3以内。

下面 n-1 行,每行一组u,v,表示一条边。(保证初始为一棵树)

下面 m 行有 m 个操作:

先读入一个 opt,表示操作类型。

  • opt=1 时,读入 e,表示删掉读入的第 e 条边。(保证第 e 条边存在)

  • opt=2 时,读入u,val,表示把结点 u 的权值改成 val(val≤1000)。

  • opt=3 时,读入 u,表示查询 u 所在的那棵树的结点权值和。

输出格式

对于每个查询操作,输出一行一个数表示答案。

输入输出样例

输入 #1复制

2 3
1 1
1 2
2 2 4
1 1
3 2

输出 #1复制

4

说明/提示

所有测试数据数据满足 1≤n,m≤1e5,1≤ai​,val≤1000。

思路:看到删边倒序并查集,小菜鸡也还不是很理解......(盗用大佬发言看到需要删边,且无删边操作,可以想到并查集倒序加边维护整个连通块。)

#include<bits/stdc++.h>
using namespace std;
struct node{
	int u,v;
}e[100005];//保存边结点
int num[100005];//保存每个结点的权值
int sum[100005];//保存以该结点为祖先的权值总和
int ans[100005];//保存答案,因为是逆序操作保存答案的,最终要逆序输出
int vis[100005];
vector<int>	val[100005];//保存每个结点的历史值,每修改一次就插入到尾部                      
                       //逆序操作到的时候从尾部取就行了
int f[100005];//保存祖先
struct cz{//保存每一次操作以及操作的结点
	int opt;
	int u;
}x[100005];
int cnt;
int find(int x)//基础并查集
{
	return x==f[x]?x:f[x] = find(f[x]);
}
void merge(int x,int y)
{
	int xx = find(x),yy = find(y);
	if(xx!=yy)
	{
		f[xx] = yy;
		sum[yy]+=sum[xx];//合并权值之和
		sum[xx] = 0;
	}
}
int n,m;
int main()
{
	cin>>n>>m;
	for(int i = 1;i<=n;i++)cin>>num[i];
	for(int i = 1;i<n;++i)cin>>e[i].u>>e[i].v;//输入边
	for(int i = 1;i<=m;++i)//输入操作
	{
		cin>>x[i].opt;
		if(x[i].opt==1)
		{
			cin>>x[i].u;
			vis[x[i].u] = 1;
		}
		if(x[i].opt==2)
		{
			cin>>x[i].u;
			int v;
			cin>>v;
			val[x[i].u].push_back(num[x[i].u]); //将该结点当前权值插入到最新历史版本中
			num[x[i].u] = v;//更新权值
		}
		if(x[i].opt==3)
			cin>>x[i].u;
	}
	for(int i = 1;i<=n;i++)
		sum[i] = num[i],f[i] = i;
	for(int i = 1;i<n;++i)
	{
		if(!vis[i])
			merge(e[i].u,e[i].v);
	}
	for(int i = m;i>0;i--)//逆序操作
	{
		if(x[i].opt==1)//碰到断边的就连回去
			merge(e[x[i].u].u,e[x[i].u].v);
		if(x[i].opt==2)
		{
			int v = x[i].u;
			int now = val[v][val[v].size()-1];
			val[v].pop_back();
			sum[find(v)]+=now-num[v];//更新该树的权值总和
			num[v] = now;//返回历史的最新版本
		}
		if(x[i].opt==3)
		{
			ans[++cnt] = sum[find(x[i].u)];//不多赘述
		}
	}
	for(int i = cnt;i>0;i--)//逆序输出答案
		cout<<ans[i]<<endl;
	return 0;
}

白色旅人 

省选/NOI- 这。。。只能看大佬A题了。。。 

T293037 [传智杯 #5 练习赛] 白色旅人 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

有一个物品队列B,初始时为空。现在共有三种操作。每个操作会给定三个整数 op,x,y,其中 op 表示操作种类,x,y 是操作的参数。操作分为如下三种:

  • 11:向B 尾部添加一个物品,它的体积为 x,价值为 y。
  • 22:把B 尾部最后物品移除。保证此时最少有一个物品。
  • 33:有一个体积为 x 的背包。用B 内的物品填充它,每个物品最多用一次,询问最多能获得多大的价值。提示:当B 为空时,相当于没有物品可用,答案就是 0。

对于操作 2 和 3,请忽略多余的参数。

本题强制在线。强制在线的方式请见输入格式。

输入格式

  • 第一行有两个正整数 n,m_max​,表示操作个数以及操作 3 提到的背包的体积的最大值。
  • 接下来 n行,每行给定三个整数op,x′,y′。将 x′,y′ 分别异或上lastans 得到该次询问真正的x,y。其中,lastans 是上一次操作 3 询问的结果。在第一次操作 3 前,lastans=0。

输出格式

  • 输出有若干行,表示每次 3 操作的结果。

输入输出样例

输入 #1复制

10 10
1 3 4
1 5 5
3 4 1
3 12 5
1 14 3
3 1 8
2 11 11
3 2 11
2 8 8
3 12 8

输出 #1复制

4
9
10
9
4

说明/提示

样例解释

解码后的输入数据为:

10 10
1 3 4
1 5 5
3 4 1
3 8 1
1 7 10
3 8 1
2 1 1
3 8 1
2 1 1
3 5 1

对于十次操作,物品序列的情况如下;

  • 加入体积为 3,价值为 4 的物品。物品序列为 {(3,4)}。
  • 加入体积为 5,价值为 5 的物品。物品序列为{(3,4),(5,5)}。
  • 查询体积为 4 的背包能装下的物品价值最大值。此时只能装第一个物品,于是答案为 4。
  • 查询体积为 8 的背包能装下的物品价值最大值。此时可把两个物品都装下,答案为 9。
  • 加入体积为7,价值为10 的物品。物品序列为{(3,4),(5,5),(7,10)}。
  • 查询体积为 8 的背包能装下的物品价值最大值。此时直接装第三个物品获得的价值大于装下另外两个,于是答案为 10。
  • 删除最后一个物品。此时物品序列为{(3,4),(5,5)}。
  • 查询体积为 8 的背包能装下的物品价值最大值。此时可把两个物品都装下,答案为 9。
  • 删除最后一个物品。此时物品序列为{(3,4)}。
  • 查询体积为 5 的背包能装下的物品价值最大值。此时只有一个物品可装,答案为 4。

数据范围及约定

对于全部数据,1≤n≤3×10^4,1≤mmax​≤2×10^4,1≤x,y≤2×10^4。

写不来写不来。。。弄不懂了只能说。 贴一下官方给出的

#include<bits/stdc++.h>
#define up() l到r的for循环
#define down() r到l的for循环
using namespace std;
typedef long long i64;
const int INF = 2147483647;
const int MAXN = 3e4 + 3, MAXV = 2e4 + 3, MAXM = 300 + 3, SI = 4;
int q, m, t, s, l, r, X[MAXN], Y[MAXN];
int W[MAXM][MAXV], M[MAXM][MAXV];
int qread(){
    快速读入
}
int main(){
    q = qread(), m = qread(); int lastans = 0;
    s = 1 + sqrt(q + 1) / 2, l = 0, r = 2 * s - 1;
    up(1, q, i){
        int op = qread(), x = qread() ^ lastans, y = qread() ^ lastans;
        if(op == 1){
            ++ t; X[t] = x, Y[t] = y;
            if(t - 1 == r){
                up(0, s - 1, j) up(0, m, k) W[j][k] = W[j + s][k];
                l += s, r += s;
            }
            int u = min(x - 1, m);
            up(0, u, j) W[t - l][j] = W[t - l - 1][j];
            up(x, m, j) W[t - l][j] = max(W[t - l - 1][j], W[t - l - 1][j - x] + y);
            if(t % s == 0) up(0, m, j) M[t / s][j] = W[t - l][j];
        } else if(op == 2){
            -- t;
            if(t + 1 == l){
                l -= s, r -= s;
                up(0, m, j) W[0][j] = M[l / s][j];
                up(1, s - 1, j){
                    int x = X[l + j], y = Y[l + j], u = min(x - 1, m);
                    up(0, u, k) W[j][k] = W[j - 1][k];
                    up(x, m, k) W[j][k] = max(W[j - 1][k], W[j - 1][k - x] + y);
                }
            }
        } else {
            printf("%d\n", lastans = W[t - l][x]);
        }
    }
    return 0;
}

  • 11
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值