2023 CSP-S提高组答案

1.密码锁(lock) http://ybt.ssoier.cn:8088/problem_show.php?pid=2102

2.消消乐(game)http://ybt.ssoier.cn:8088/problem_show.php?pid=2103

3.结构体(struct)http://ybt.ssoier.cn:8088/problem_show.php?pid=2104

4.种树(tree)信息学奥赛一本通(C++.版)在线评测系统


1.密码锁(lock)

【题目描述】

你是一个森林养护员,有一天,你接到了一个任务:在一片森林内的地块上种树,并养护至树木长到指定的高度。

森林的地图有 nn 片地块,其中 11 号地块连接森林的入口。共有 n−1n−1 条道路连接这些地块,使得每片地块都能通过道路互相到达。最开始,每片地块上都没有树木。

你的目标是:在每片地块上均种植一棵树木,并使得 ii 号地块上的树的高度生长到不低于 aiai 米。

你每天可以选择一个未种树且与某个已种树的地块直接邻接即通过单条道路相连)的地块,种一棵高度为 00 米的树。如果所有地块均已种过树,则你当天不进行任何操作。特别地,第 11 天你只能在 11 号空地种树。

对每个地块而言,从该地块被种下树的当天开始,该地块上的树每天都会生长一定的高度。由于气候和土壤条件不同,在第 xx 天,ii 号地块上的树会长高 max(bi+x×ci,1)max(bi+x×ci,1) 米。注意这里的 xx 是从整个任务的第一天,而非种下这棵树的第一天开始计算。

你想知道:最少需要多少天能够完成你的任务?

【输入】

输入的第一行包含一个正整数 nn,表示森林的地块数量。

接下来 nn 行:每行包含三个整数 ai,bi,ciai,bi,ci,分别描述一片地块,含义如题目描述中所述。

接下来 n−1n−1 行:每行包含两个正整数 ui,viui,vi,表示一条连接地块 uiui 和 vivi 的道路。

【输出】

输出一行仅包含一个正整数,表示完成任务所需的最少天数。

【输入样例】

4
12 1 1
2 4 -1
10 3 0
7 10 -2
1 2
1 3
3 4

【输出样例】

5

【提示】

【样例 1 解释】

第 11 天:在地块 11 种树,地块 11 的树木长高至 22 米。

第 22 天:在地块 33 种树,地块 1,31,3 的树木分别长高至 5,35,3 米。

第 33 天:在地块 44 种树,地块 1,3,41,3,4 的树木分别长高至 9,6,49,6,4 米。

第 44 天:在地块 22 种树,地块 1,2,3,41,2,3,4 的树木分别长高至 14,1,9,614,1,9,6 米。

第 55 天:地块 1,2,3,41,2,3,4 的树木分别长高至 20,2,12,720,2,12,7 米。

【数据范围】

对于所有测试数据有:1≤n≤105,1≤ai≤1018,1≤bi≤109,0≤|ci|≤109,1≤ui,vi≤n1≤n≤105,1≤ai≤1018,1≤bi≤109,0≤|ci|≤109,1≤ui,vi≤n。保证存在方案能在 109109天内完成任务。

测试点编号n≤特殊性质
120A
2~4
5~6500A
7~8105
9~10B
11~13C
14~16D
17~20

特殊性质 A:对于所有 1≤i≤n1≤i≤n,均有 ci=0ci=0;

特殊性质 B:对于所有 1≤i<n1≤i<n,均有 ui=iui=i、vi=i+1vi=i+1;

特殊性质 C:与任何地块直接相连的道路均不超过 22 条;

特殊性质 D:对于所有 1≤i<n1≤i<n,均有 ui=1ui=1。

OK呀直接上AC代码:

#include<bits/stdc++.h>
using namespace std;
int n,a[10],ans;
map<int,int>mp;
void work(){
	int res=0;
	for(int i=1,pw=1;i<=5;i++,pw*=10)res+=a[i]*pw;
	mp[res]++;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=5;j++)cin>>a[j];
		for(int j=1;j<=5;j++){
			for(int k=1;k<10;k++){
				a[j]=(a[j]+k)%10;
				work();
				a[j]=(a[j]-k+10)%10;
			}
		}
		for(int j=1;j<5;j++){
			for(int k=1;k<10;k++){
				a[j]=(a[j]+k)%10;
				a[j+1]=(a[j+1]+k)%10;
				work();
				a[j]=(a[j]-k+10)%10;
				a[j+1]=(a[j+1]-k+10)%10;	
			}
		}
	}
	for(auto tmp:mp)if(tmp.second==n)ans++;
	cout<<ans<<"\n";
	return 0;
}

2.消消乐(game)

【题目描述】

小 L 现在在玩一个低配版本的消消乐,该版本的游戏是一维的,一次也只能消除两个相邻的元素。

现在,他有一个长度为 nn 且仅由小写字母构成的字符串。我们称一个字符串是可消除的,当且仅当可以对这个字符串进行若干次操作,使之成为一个空字符串。

其中每次操作可以从字符串中删除两个相邻的相同字符,操作后剩余字符串会拼接在一起。

小 L 想知道,这个字符串的所有非空连续子串中,有多少个是可消除的。

【输入】

输入的第一行包含一个正整数 nn,表示字符串的长度。

输入的第二行包含一个长度为 nn 且仅由小写字母构成的的字符串,表示题目中询问的字符串。

【输出】

输出一行包含一个整数,表示题目询问的答案。

【输入样例】

8
accabccb

【输出样例】

5

【提示】

【样例 1 解释】

一共有 55 个可消除的连续子串,分别是ccaccaccbccbaccabccb

【数据范围】

对于所有测试数据有:1≤n≤2×1061≤n≤2×106,

且询问的字符串仅由小写字母构成。

测试点n≤n≤特殊性质
1∼51∼51010
6∼76∼7800800
8∼108∼1080008000
11∼1211∼122×1052×105A
13∼1413∼142×1052×105B
15∼1715∼172×1052×105
18∼2018∼202×1062×106

特殊性质 A:字符串中的每个字符独立等概率地从字符集中选择。

特殊性质 B:字符串仅由 a 和 b 构成。

OK呀直接上AC代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e6+5;
int n,dp[N],a[N][26],to[N];
char s[N];
ll ans;
int main()
{
    scanf("%d%s",&n,s+1);
    for(int i=1;i<=n;i++)
    {
        to[i]=i;
        int x=a[to[i-1]][s[i]-'a'];
        if(x) to[i]=to[x-1],dp[i]=dp[x-1]+1;
        a[to[i]][s[i]-'a']=i,ans+=dp[i];
    }
    printf("%lld\n",ans);
    return 0;
}

3.结构体(struct)

【题目描述】

在 C++ 等高级语言中,除了 int 和 float 等基本类型外,通常还可以自定义结构体类型。在本题当中,你需要模拟一种类似 C++ 的高级语言的结构体定义方式,并计算出相应的内存占用等信息。

在这种语言中,基本类型共有 4 种:byte、short、int、long,分别占据 1、2、4、8 字节的空间。

定义一个结构体类型时,需要给出类型名和成员,其中每个成员需要按顺序给出类型和名称。类型可以为基本类型,也可以为先前定义过的结构体类型。注意,定义结构体类型时不会定义具体元素,即不占用内存。

定义一个元素时,需要给出元素的类型和名称。元素将按照以下规则占据内存:

元素内的所有成员将按照定义时给出的顺序在内存中排布,对于类型为结构体的成员同理。

为了保证内存访问的效率,元素的地址占用需要满足对齐规则,即任何类型的大小和该类型元素在内存中的起始地址均应对齐到该类型对齐要求的整数倍。具体而言:

对于基本类型:对齐要求等于其占据空间大小,如 int 类型需要对齐到4 字节,其余同理。

对于结构体类型:对齐要求等于其成员的对齐要求的最大值,如一个含有 int 和 short 的结构体类型需要对齐到 4 字节。

以下是一个例子(以 C++ 语言的格式书写):

struct d {
    short a;
    int b;
    short c;
};
d e;

该代码定义了结构体类型 d 与元素 e。元素 e 包含三个成员 e.a、e.b、e.c,分别占据第0∼1、4∼7、8∼9 字节的地址。由于类型 d 需要对齐到4 字节,因此 e 占据了第 0∼11 字节的地址,大小为 12 字节。

你需要处理 n 次操作,每次操作为以下四种之一:

1.定义一个结构体类型。具体而言,给定正整数 kk 与字符串ss,t1t1,…,n1n1,tktk,nknk ,其中kk 表示该类型的成员数量,ss 表示该类型的类型名,t1t1,t2t2…,tktk  按顺序分别表示每个成员的类型,n1n1,n2n2,…,nknk 按顺序分别表示每个成员的名称。你需要输出该结构体类型的大小和对齐要求,用一个空格分隔。

2.定义一个元素,具体而言,给定字符串 ,tt,nn 分别表示该元素的类型与名称。所有被定义的元素将按顺序,从内存地址为 0 开始依次排开,并需要满足地址对齐规则。你需要输出新定义的元素的起始地址。

3.访问某个元素。具体而言,给定字符串 ss,表示所访问的元素。与 C++ 等语言相同,采用 . 来访问结构体类型的成员。如 a.b.ca.b.c,表示 aa 是一个已定义的元素,它是一个结构体类型,有一个名称为 bb 的成员,它也是一个结构体类型,有一个名称为 cc 的成员。你需要输出如上被访问的最内层元素的起始地址。

4.访问某个内存地址。具体而言,给定非负整数 addraddr,表示所访问的地址,你需要判断是否存在一个基本类型的元素占据了该地址。若是,则按操作 3 中的访问元素格式输出该元素;否则输出 ERR。

【输入】

第 1 行:一个正整数 nn,表示操作的数量。

接下来若干行,依次描述每个操作,每行第一个正整数 opop 表示操作类型:

若op=1op=1,首先输入一个字符串ss 与一个正整数 kk,表示类型名与成员数量,接下来 kk 行每行输入两个字符串titi,nini ,依次表示每个成员的类型与名称。

若 op=2op=2,输入两个字符串 tt,nn,表示该元素的类型与名称。

若 op=3op=3,输入一个字符串 ss,表示所访问的元素。

若op=4op=4,输入一个非负整数 addraddr,表示所访问的地址。

【输出】

输出 nn 行,依次表示每个操作的输出结果,输出要求如题目描述中所述。

【输入样例】

5
1 a 2
short aa
int ab
1 b 2
a ba
long bb
2 b x
3 x.ba.ab
4 10

【输出样例】

8 4
16 8
0
4
x.bb

【提示】

说明/提示

【样例 1 解释】

结构体类型 a 中,short 类型的成员 aa 占据第 0∼1 字节地址,int 类型的成员 ab 占据第 4∼7 字节地址。又由于其对齐要求为 4 字节,可得其大小为 8 字节。由此可同理计算出结构体类型 b 的大小为 16 字节,对齐要求为 8 字节。

【数据范围】

对于全部数据,满足 1≤n≤1001≤n≤100,1≤k≤1001≤k≤100,0≤addr≤10180≤addr≤1018 。

所有定义的结构体类型名、成员名称和定义的元素名称均由不超过 10 个字符的小写字母组成,且都不是 byte,short,int,long(即不与基本类型重名)。

所有定义的结构体类型名和元素名称互不相同,同一结构体内成员名称互不相同。但不同的结构体可能有相同的成员名称,某结构体内的成员名称也可能与定义的结构体或元素名称相同。

保证所有操作均符合题目所述的规范和要求,即结构体的定义不会包含不存在的类型、不会访问不存在的元素或成员等。

保证任意结构体大小及定义的元素占据的最高内存地址均不超过 10181018 。

测试点特殊性质
1A、D
2∼ 3A
4∼ 5B、D
6∼ 8B
9∼ 10C、D
11∼ 13C
14∼ 16D
17∼ 20

特殊性质 A:没有操作 11;

特殊性质 B:只有一个操作 11;

特殊性质 C:所有操作 11 中给出的成员类型均为基本类型;

特殊性质 D:基本类型只有 long。

【特别提示】

对于结构体类型的对齐要求和大小,形式化的定义方式如下:

设该结构体内有 kk 个成员,其大小分别为 s1,...,sks1,...,sk,对齐要求分别为 a1,...,aka1,...,ak;

则该结构体的对齐要求为 a=max{a1,...,ak}a=max{a1,...,ak};

再设这些成员排布时的**地址偏移量**分别为 o1,...,oko1,...,ok,则:

o1=0o1=0;

对于 i=2,...,ki=2,...,k,oioi 为满足 oi−1+si−1≤oioi−1+si−1≤oi 且 aiai 整除 oioi 的最小值;

则该结构体的大小 ss 为满足 ok+sk≤sok+sk≤s 且 aa 整除 ss 的最小值;

对于定义元素时的内存排布,形式化的定义方式如下:

设第 ii 个被定义的元素大小为 sisi,对齐要求为 aiai,起始地址为 bibi;

则 b1=0b1=0,对于 2≤i2≤i, bibi 为满足 bi−1+si−1≤bibi−1+si−1≤bi 且 aiai 整除 bibi 的最小值。

OK呀直接上AC代码:

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define fi first
#define sc second
using namespace std;
#define int long long //为了保险开的,不少人没开ll痛失40分
/*
	结构体、变量的定义和各个函数的实现方式省略。
*/
signed main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int n; cin>>n;
    mp["long"]=jiegou(8ll,"long");
    mp["int"]=jiegou(4ll,"int");
    mp["short"]=jiegou(2ll,"short");
    mp["byte"]=jiegou(1ll,"byte");
    rep(idk,1,n){
        int opt; cin>>opt;
        if(opt==1){
            string x; int y; cin>>x>>y;
            build(x,y);
        }
        else if(opt==2){
            string x,y; cin>>x>>y; add(x,y);
        }
        else if(opt==3){
            string x; cin>>x; cout<<find(x)<<endl;
        }
        else if(opt==4){
            int adr; cin>>adr;
            string ans=getbyadr(tr,0,adr);
            if(ans.find("ERR")!=-1) cout<<"ERR"<<endl;
            //一个取巧的方法(因为错误时其实上只有最后一层是ERR)
            else cout<<ans<<endl;
        }
    }
}

4.种树(tree)

【题目描述】

你是一个森林养护员,有一天,你接到了一个任务:在一片森林内的地块上种树,并养护至树木长到指定的高度。

森林的地图有 n� 片地块,其中 11 号地块连接森林的入口。共有 n−1�−1 条道路连接这些地块,使得每片地块都能通过道路互相到达。最开始,每片地块上都没有树木。

你的目标是:在每片地块上均种植一棵树木,并使得 i� 号地块上的树的高度生长到不低于 ai�� 米。

你每天可以选择一个未种树且与某个已种树的地块直接邻接即通过单条道路相连)的地块,种一棵高度为 00 米的树。如果所有地块均已种过树,则你当天不进行任何操作。特别地,第 11 天你只能在 11 号空地种树。

对每个地块而言,从该地块被种下树的当天开始,该地块上的树每天都会生长一定的高度。由于气候和土壤条件不同,在第 x� 天,i� 号地块上的树会长高 max(bi+x×ci,1)max(��+�×��,1) 米。注意这里的 x� 是从整个任务的第一天,而非种下这棵树的第一天开始计算。

你想知道:最少需要多少天能够完成你的任务?

【输入】

输入的第一行包含一个正整数 n�,表示森林的地块数量。

接下来 n� 行:每行包含三个整数 ai,bi,ci��,��,��,分别描述一片地块,含义如题目描述中所述。

接下来 n−1�−1 行:每行包含两个正整数 ui,vi��,��,表示一条连接地块 ui�� 和 vi�� 的道路。

【输出】

输出一行仅包含一个正整数,表示完成任务所需的最少天数。

【输入样例】

4
12 1 1
2 4 -1
10 3 0
7 10 -2
1 2
1 3
3 4

【输出样例】

5

【提示】

【样例 1 解释】

第 11 天:在地块 11 种树,地块 11 的树木长高至 22 米。

第 22 天:在地块 33 种树,地块 1,31,3 的树木分别长高至 5,35,3 米。

第 33 天:在地块 44 种树,地块 1,3,41,3,4 的树木分别长高至 9,6,49,6,4 米。

第 44 天:在地块 22 种树,地块 1,2,3,41,2,3,4 的树木分别长高至 14,1,9,614,1,9,6 米。

第 55 天:地块 1,2,3,41,2,3,4 的树木分别长高至 20,2,12,720,2,12,7 米。

【数据范围】

对于所有测试数据有:1≤n≤105,1≤ai≤1018,1≤bi≤109,0≤|ci|≤109,1≤ui,vi≤n1≤�≤105,1≤��≤1018,1≤��≤109,0≤|��|≤109,1≤��,��≤�。保证存在方案能在 109109 天内完成任务。

测试点编号n≤特殊性质
120A
2~4
5~6500A
7~8105
9~10B
11~13C
14~16D
17~20

特殊性质 A:对于所有 1≤i≤n1≤�≤�,均有 ci=0��=0;

特殊性质 B:对于所有 1≤i<n1≤�<�,均有 ui=i��=�、vi=i+1��=�+1;

特殊性质 C:与任何地块直接相连的道路均不超过 22 条;

特殊性质 D:对于所有 1≤i<n1≤�<�,均有 ui=1��=1。

OK呀直接上AC代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5, E = N << 1;
const long long Max = 1e9;
typedef pair<long long, int> pir;
int n;
long long a[N + 5], b[N + 5], c[N + 5], zero[N + 5];
int head[N + 5], to[E + 5], nxt[E + 5], tot = 1;
void add_edge(int u, int v){
	tot++;
	to[tot] = v;
	nxt[tot] = head[u];
	head[u] = tot;
	return ;
}
void add(int u, int v){
	add_edge(u, v);
	add_edge(v, u);
	return ;
}
long long d[N + 5];//limit
int sz[N + 5];
void calc_d(int u, long long ans){
	long long l = 1, r = n;
	d[u] = -1ll;
	while (l <= r){
		long long mid = (l + r) >> 1;
		__int128 sum = 0, one = 1;
		if (c[u] >= 0)
			sum = one * (ans - mid + 1) * b[u]
				 + one * (mid + ans) * (ans - mid + 1) / 2 * c[u];
		else{
			if (mid > zero[u])
				sum = ans - mid + 1;
			else if (ans > zero[u])
				sum = one * (zero[u] - mid + 1) * b[u]
					 + one * (mid + zero[u]) * (zero[u] - mid + 1) / 2 * c[u]
					 + ans - zero[u];
			else
				sum = one * (ans - mid + 1) * b[u]
					 + one * (mid + ans) * (ans - mid + 1) / 2 * c[u];
		}

		if (one * a[u] <= sum){
			d[u] = mid;
			l = mid + 1;
		}
		else
			r = mid - 1;
	}
	return ;
}

int fa[N + 5], in[N + 5];
void dfs(int u, int father)
{
	fa[u] = father;
	in[father]++;
	for (int i = head[u]; i; i = nxt[i])
	{
		int v = to[i];
		if (v == father)
			continue;
		dfs(v, u);
	}
	return ;
}

priority_queue<pir> q;
int seq[N + 5];
bool check(long long ans)
{
	for (int i = 1; i <= n; i++)
	{
		calc_d(i, ans);
		if (d[i] < 0)
			return false;
	}

	dfs(1, 0);
	for (int i = 1; i <= n; i++)
	{
		if (in[i] == 0)
			q.emplace(d[i], i);
	}

	for (int T = n; T > 0; T--)
	{
		int u = q.top().second;
		q.pop();
		seq[T] = u;

		if (fa[u])
		{
			in[fa[u]]--;
			if (in[fa[u]] == 0)
				q.emplace(d[fa[u]], fa[u]);
		}
	}

	for (int i = 1; i <= n; i++)
	{
		if (1ll * i > d[seq[i]])
			return false;
	}
	return true;
}

int main()
{
	// freopen("tree.in", "r", stdin);
	// freopen("tree.out", "w", stdout);

	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%lld%lld%lld", a + i, b + i, c + i);
		if (c[i] < 0)
			zero[i] = (1ll - b[i]) / c[i];
	}
	for (int i = 1, u, v; i < n; i++)
	{
		scanf("%d%d", &u, &v);
		add(u, v);
	}

	long long l = 1, r = Max, ans = 0;
	while (l <= r)
	{
		long long mid = (l + r) >> 1;
		if (check(mid))
		{
			ans = mid;
			r = mid - 1;
		}
		else
			l = mid + 1;
	}
	printf("%lld\n", ans);
	return 0;
}

制作花了2.3小时,请给个关注点点赞吧,准备睡觉了~~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值