2025 天梯赛 PTA 模拟赛 题解答案 L1+L2+L3

2025 天梯赛 PTA 模拟赛 题解答案 L1+L2+L3

声明

  • 不保证为官方标算,若有更优解法恳请大佬指点。
  • 图片来自 PTA 习题集。

L1题解

L1-1 爱您爱我

题目描述

2025 2025 2025 谐音是 “爱您爱我”。本题就请你用汉语拼音输出这句话 2025 - ai nin ai wo

解题思路
  • 直接输出即可。
完整代码
#include<iostream>
using namespace std;
int main(){
	cout << "2025 - ai nin ai wo";
	return 0;
}

L1-2 九牛一毛

题目描述

在这里插入图片描述

输入格式

输入在一行中给出一个不超过 1000 1000 1000 的正整数 N N N,即以 “元” 为单位的货币量。

输出格式

在一行中顺序输出 N N N 块钱能买多少斤猪肉、多少斤鸡肉、多少头牛。三个数字都只取整数部分,其间以 1 1 1 个空格分隔,行首尾不得有多余空格。

解题思路
  • 按题意直接计算即可。
完整代码
#include<iostream>
using namespace std;
int main(){
	int n; cin >> n;
	printf("%d %d %d",n/15,n/20,n*10*9);
	return 0;
}

L1-3 陈依涵的高情商体重计算器

题目描述

在这里插入图片描述

输入格式

输入第一行给出一个用户输入的体重值 w w w,以斤为单位,是一个不超过 500 500 500 的正整数。

输出格式

在一行中输出:Gong xi nin! Nin de ti zhong yue wei: x duo jin,其中的 x 就是按照题面给出的规则计算出的结果。

样例输入 1
88
样例输出 1
Gong xi nin! Nin de ti zhong yue wei: 80 duo jin
样例输入 2
90
样例输出 2
Gong xi nin! Nin de ti zhong yue wei: 80 duo jin
样例输入 3
199
样例输出 3
Gong xi nin! Nin de ti zhong yue wei: 100 duo jin
解题思路
  • 依题意,大于 100 100 100 斤输出 100 100 100 斤,否则输出 w − 1 w-1 w1 后抹去个位数即可。
完整代码
#include<iostream>
using namespace std;
int main(){
	int n; cin >> n;
	if(n>100) printf("Gong xi nin! Nin de ti zhong yue wei: 100 duo jin");
	else printf("Gong xi nin! Nin de ti zhong yue wei: %d duo jin",(n-1)/10*10);
	return 0;
}

L1-4 记小本本

题目描述

在这里插入图片描述

输入格式

输入由一系列 0 0 0 1 1 1 组成,每个数字占一行。 1 1 1 代表红色按钮被按下, 0 0 0 代表绿色按钮被按下。当出现任何一个既不是 0 0 0 也不是 1 1 1 的数字时,表示他们把电源线扯断了,输入结束,最后那个数字不要处理。

输出格式

对每一个输入的 0 0 0,在一行中输出这次按下绿色按钮之前一共吵了多少次架。
题目保证每个输出的数字均不超过 1 0 4 10^4 104

样例输入
1
1
1
0
1
1
0
1
2
样例输出
3
5
解题思路
  • 依次输入,遇到 1 1 1 计数加一,遇到 0 0 0 输出当前计数。除此之外直接退出。
完整代码
#include<iostream>
using namespace std;
int main(){
	int cnt = 0;
	while(true){
		int x; scanf("%d",&x);
		if(x!=1 && x!=0) break;
		if(x == 1) cnt++;
		else printf("%d\n",cnt);
	}
	return 0;
}

L1-5 吉老师的回归

题目描述

在这里插入图片描述

输入格式

输入第一行是两个正整数 N , M N,M N,M ( 1 ≤ M ≤ N ≤ 30 1\le M\le N\le30 1MN30),表示本次天梯赛有 N N N 道题目,吉老师现在做完了 M M M 道。

接下来 N N N 行,每行是一个符合题目描述的字符串,表示天梯赛的题目内容。吉老师会按照给出的顺序看题——第一行就是吉老师看的第一道题,第二行就是第二道,以此类推。

输出格式

在一行中输出吉老师当前正在做的题目对应的题面(即做完了 M M M 道题目后,吉老师正在做哪个题)。如果吉老师已经把所有他打算做的题目做完了,输出一行 Wo AK le

样例输入 1
5 1
L1-1 is a qiandao problem.
L1-2 is so...easy.
L1-3 is Easy.
L1-4 is qianDao.
Wow, such L1-5, so easy.
样例输出 1
L1-4 is qianDao.
样例输入 2
5 4
L1-1 is a-qiandao problem.
L1-2 is so easy.
L1-3 is Easy.
L1-4 is qianDao.
Wow, such L1-5, so!!easy.
样例输出 2
Wo AK le
解题思路
  • 依题意,找到第 m + 1 m+1 m+1 个不包含 qiandaoeasy 的字符串。

    若不存在第 m + 1 m+1 m+1 个,则输出 Wo AK le

  • 那么我们每找到一个满足题意的字符串就将 m − 1 m-1 m1。在某次找到字符串时,若 m = 0 m=0 m=0 说明此时为第 m + 1 m+1 m+1 个字符串,输出后直接结束程序即可。

完整代码
#include<iostream>
using namespace std;
int main(){
	int n,m; cin >> n >> m;
	string str; getline(cin,str);	// 把第一行 n 和 m 后面的回车读掉
	while(n--){
		getline(cin,str);
		if(str.find("qiandao")!=-1 || str.find("easy")!=-1) continue;
		if(!m){ cout << str; return 0; } 
		m -= 1;
	}
	cout << "Wo AK le";
	return 0;
}

L1-6 大勾股定理

题目描述

在这里插入图片描述

输入格式

输入在一行中给出正整数 n n n ≤ 1 0 4 \le 10^4 104)。

输出格式

分两行输出满足大勾股定理的解,格式如下:

a[0]^2 + a[1]^2 + ... + a[n]^2 =
a[n+1]^2 + ... + a[2n]^2

其中解的数列 a[0] ... a[2n] 按递增序输出。注意行首尾不得有多余空格。

样例输入
3

样例输出
21^2 + 22^2 + 23^2 + 24^2 =
25^2 + 26^2 + 27^2

解题思路
思路一
  • 发现输入一个 n n n,答案一定是连续的 2 ∗ n + 1 2*n+1 2n+1 个正整数,前 n + 1 n+1 n+1 个的平方和等于后 n n n 个的平方和。
  • 那么我们可以用一个 滑动窗口,窗口长度为 2 ∗ n + 1 2*n+1 2n+1。每次窗口向右移一位时,等式左边的平方和只需要减去出窗口的那个数的平方,再新加上进窗口的数的平方。等式右边的平方和同理。
  • 直到某刻等式左边的平方和 等于 等式右边的平方和,此时窗口内的 2 ∗ n + 1 2*n+1 2n+1 个数即为答案。
  • 注意输出格式呀。
完整代码
#include<iostream>
using namespace std;
int main(){
	int n; scanf("%d",&n);
	int l1 = 1,r1 = n+1,l2 = n+2,r2 = 2*n+1;
	long long sum1 = 0,sum2 = 0;
	for(int i=l1;i<=r1;i++) sum1 += i*i;
	for(int i=l2;i<=r2;i++) sum2 += i*i;
	while(sum1 != sum2){
		sum1 = sum1 - l1*l1 + (r1+1)*(r1+1);
		sum2 = sum2 - l2*l2 + (r2+1)*(r2+1);
		++l1,++r1,++l2,++r2;
	}
	for(int i=l1;i<=r1;i++){
		if(i!=l1) printf(" + ");
		printf("%d^2",i);
	}printf(" =\n");
	for(int i=l2;i<=r2;i++){
		if(i!=l2) printf(" + ");
		printf("%d^2",i);
	}
	return 0;
}
思路二
  • 我们先写一个朴素的暴力:枚举每一个起始位置,再判断此时是否满足题目要求。

  • 但提交发现代码超时。若没想到思路一,我们可以找找答案存不存在什么规律。

  • n = 1 , 2 , 3...10 n=1,2,3...10 n=1,2,3...10 时的答案起始数字全部输出出来:

    n = 1 : begin = 3
    n = 2 : begin = 10
    n = 3 : begin = 21
    n = 4 : begin = 36
    n = 5 : begin = 55
    n = 6 : begin = 78
    n = 7 : begin = 105
    n = 8 : begin = 136
    n = 9 : begin = 171
    n = 10 : begin = 210
    
  • 我们不难发现,答案的起始数字为 n ∗ ( 2 ∗ n + 1 ) n*(2*n+1) n(2n+1)。所以从 n ∗ ( 2 ∗ n + 1 ) n*(2*n+1) n(2n+1) 开始的连续的 2 ∗ n + 1 2*n+1 2n+1 个数字即为答案,直接输出即可。

  • 注意输出格式呀。

完整代码
#include<iostream>
using namespace std;
int main(){
	int n; scanf("%d",&n);
	int st = n*(2*n+1);
	for(int i=0;i<=n;i++){
		if(i) printf(" + ");
		printf("%d^2",st+i);
	}printf(" =\n");
	for(int i=0;i<n;i++){
		if(i) printf(" + ");
		printf("%d^2",st+n+1+i);
	}
	return 0;
}

L1-7 乘法口诀数列

题目描述

在这里插入图片描述

输入格式

输入在一行中给出 3 个整数,依次为 a 1 a_1 a1 a 2 a_2 a2 n n n,满足 0 ≤ a 1 , a 2 ≤ 9 0\le a_1,a_2\le 9 0a1,a29 0 < n ≤ 1 0 3 0\lt n\le 10^3 0<n103

输出格式

在一行中输出数列的前 n n n 项。数字间以 1 1 1 个空格分隔,行首尾不得有多余空格。

样例输入
2 3 10
样例输出
2 3 6 1 8 6 8 4 8 4
解题思路
  • 按照题意,先生成 n n n 个数字,然后一次性输出。
  • 用一个变量 p p p 指向当前数组的末尾(新数字要插入的位置)。从前往后每次取相邻两个数字,计算二者乘积,若是一位数直接填在末尾,若是两位数则拆开填入末尾两次。
  • p > n p\gt n p>n 时直接输出整个数组即可。
  • 要注意输出格式呀。
完整代码
#include<iostream>
using namespace std;
const int N = 1e4+5;
int n,a[N];
int main(){
	scanf("%d%d%d",&a[1],&a[2],&n);
	for(int i=1,p=3;p<=n;i++){	// 生成至少 n 个数字 
		int cur = a[i]*a[i+1];
		if(cur>=10) a[p++] = cur/10,a[p++] = cur%10;	// 两位数
		else a[p++] = cur;
	}
	for(int i=1;i<=n;i++){
		if(i>1) printf(" ");	// 注意输出格式
		printf("%d",a[i]);
	}
	return 0;
}

L1-8 新年烟花

题目描述

在这里插入图片描述

输入格式

输入第一行是三个正整数 N , M , H N,M,H N,M,H ( 1 ≤ N , M ≤ 50 1\le N,M\le50 1N,M50 , 1 ≤ H ≤ 210 1\le H\le 210 1H210),表示活动场地矩阵大小为 N × M N×M N×M,小 C 的身高为 H H H

接下来的 N N N 行,每行 M M M 个整数,整数的含义如下:

  • 如果是一个正整数,则表示该格子被人或建筑物占据,高度为对应的值。
  • 如果是一个负整数,则表示该格子用于燃放烟花。所有燃放烟花的格子视为没有高度。
  • 如果是 0 0 0,则表示该格子是空格子。

所有整数的绝对值不超过 500 500 500

输出格式

输出第一行是一个正整数,表示对小 C 而言的优秀观赏位置有多少个。

接下来输出能看到最多的燃放烟花的格子的坐标 ( X , Y ) (X,Y) (X,Y),即格子在第 X X X 行、第 Y Y Y 列,数字间以 1 1 1 个空格分隔。当存在多组坐标时,只输出最小的那组,即输出 X X X 最小的解; X X X 相同时输出 Y Y Y 最小的解。

矩阵左上角坐标为 ( 0 , 0 ) (0,0) (0,0) ,保证至少存在一个对小 C 而言的优秀观赏位置。

样例输入
10 10 175
0 0 0 0 0 0 0 0 0 0
0 50 0 180 -100 180 0 70 30 0
0 30 0 0 300 0 0 0 0 0
0 250 0 0 -100 0 0 0 0 0
0 -100 174 0 0 0 0 169 -100 0
0 -100 0 0 0 0 0 0 -100 0
0 -1 0 0 170 0 0 0 0 0
0 5 0 0 300 0 0 0 0 0
0 20 0 0 -100 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0

样例输出
6
3 8
解题思路
  • 我们发现 N N N M M M 都很小,不超过 50 50 50,那么可以暴力枚举每一个坐标 ( x , y ) (x,y) (x,y),若其是空地,则计算从这个坐标可以看到的烟花数量。

  • 那么我们只需要统计有多少个位置,看到的烟花数量 ≥ 3 \ge3 3,并记录一下看到烟花数量最多的位置即可。

  • 对于一个位置 ( x , y ) (x,y) (x,y),我们站在该点,当向一个方向看去时,统计直到第一个高度大于 H H H 的格子之前,出现过多少个烟花,即是这个方向上能看到的烟花数量。

    四个方向分别加起来即为该点 ( x , y ) (x,y) (x,y) 能看到的烟花数量。

  • 由于题目要求在看到烟花数量相同时,输出最小的坐标。我们可以按题意先从小到大遍历 X X X,再从小到大遍历 Y Y Y,那么只需要在烟花数量更大时更新即可。

完整代码
#include<iostream>
using namespace std;
const int N = 55;
int n,m,c,h[N][N];
inline int check(int x,int y){
	if(h[x][y]) return 0;	// 若不为空地,返回 0
	int res = 0;
	for(int i=1;x-i>=0;i++){	// 分别统计四个方向上的答案
		if(h[x-i][y]>=c) break;	
		if(h[x-i][y]<0) res++;
	}
	for(int i=1;x+i<=n;i++){
		if(h[x+i][y]>=c) break;
		if(h[x+i][y]<0) res++;
	}
	for(int i=1;y-i>=0;i++){
		if(h[x][y-i]>=c) break;
		if(h[x][y-i]<0) res++;
	}
	for(int i=1;y+i<=m;i++){
		if(h[x][y+i]>=c) break;
		if(h[x][y+i]<0) res++;
	}
	return res;
}
int main(){
	scanf("%d%d%d",&n,&m,&c);
	for(int i=0;i<n;i++) 
		for(int j=0;j<m;j++)
			scanf("%d",&h[i][j]);
	int res = 0,x = -1,y = -1,cnt = 0;	//res 为最大的烟花数,(x,y 记录此时的位置)
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			int ans = check(i,j);
			if(ans < 3) continue;
			cnt +=1;		// 优秀的观赏位置
			if(ans > res) res = ans, x = i, y = j;	// 更新最大数量的位置
		}
	}
	printf("%d\n%d %d",cnt,x,y);
	return 0;
}

L2题解

L2-1 包装机

题目描述

在这里插入图片描述
在这里插入图片描述

输入格式

在这里插入图片描述

输出格式

在一行中输出能够被批准的最大申请数量。

样例输入
7
18:00:01 23:07:01
04:09:59 11:30:08
11:35:50 13:00:00
23:45:00 23:55:50
13:00:00 17:11:22
06:30:50 11:42:01
17:30:00 23:50:00
样例输出
5
样例说明

除了最后两个申请,其它都可以被批准。

解题思路
  • 区间调度问题,一个简单的贪心。

  • 首先显然的,若两个区间的起始时间相同,为了选更多的区间,此时我们必然选择终止时间更早的区间。

    其次由于选择的区间严格不能重叠,所以我们可以从左往右(时间轴)依次选择结束时间最早的区间,此时在这种贪心思路下可以取得的区间数便是最大数量。

  • 那么我们可以先将输入的时间转化为以秒为单位,随后根据区间的右端点从小到大排序,在结束时间相同时根据区间的左端点从大到小排序。

  • 最后从小到大遍历一遍所有区间,能选时就立刻选,便能得到最大值。

完整代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2e3+5;
int n;
struct Node{
	int l,r;
	// 先按结束时间从小到大排序,相同时按开始时间从大到小
	bool operator < (const Node y) const{	 
		return r == y.r ? l > y.l : r < y.r;
	}
}a[N];
int main(){
	scanf("%d",&n);
	for(int i=1,hl,ml,sl,hr,mr,sr;i<=n;i++){
		scanf("%d:%d:%d %d:%d:%d",&hl,&ml,&sl,&hr,&mr,&sr);
		a[i].l = hl*3600+ml*60+sl;	// 单位转化
		a[i].r = hr*3600+mr*60+sr;
	}
	sort(a+1,a+1+n);	
	int res = 0, last = -1;	 // 记录当前最右边的区间的结束时间
	for(int i=1;i<=n;i++){
		if(a[i].l >= last) res++, last = a[i].r;
	}
	printf("%d",res);
	return 0;
}

L2-3 完全二叉树的层序遍历

题目描述

在这里插入图片描述

输入格式

输入在第一行中给出正整数 N N N ≤ 30 \le30 30),即树中结点个数。第二行给出后序遍历序列,为 N N N 个不超过 100 100 100 的正整数。同一行中所有数字都以空格分隔。

输出格式

在一行中输出该树的层序遍历序列。所有数字都以 1 1 1 个空格分隔,行首尾不得有多余空格。

样例输入
8
91 71 2 34 10 15 55 18
样例输出
18 34 55 71 2 10 15 91
解题思路
  • 由于是一颗完全二叉树,除了最后一层前面每一层都填满了节点。

    故我们可以计算出当前节点的左子树的节点个数,以及右子树的节点个数。

  • 由后序遍历的性质,左子树的所有节点先出现,然后右子树所有节点出现,最后出现根节点。

  • 那么我们递归建树,最后跑一遍 b f s bfs bfs 求出层序遍历即可。

完整代码
#include<iostream>
#include<queue>
using namespace std;
const int N = 55;
int n,a[N],ls[N],rs[N],val[N],idx;
int build(int l,int r){		// 由完全二叉树的后序遍历建树
	int u = ++idx, siz = r-l+1;	// 当前树的大小
	val[u] = a[r];
	if(siz == 1) return idx; // 叶节点
	int pre = 1;	// 倒数第二层的个数
	for(int i=1;i+(pre<<1)<siz;pre<<=1,i+=pre);
	int last = siz - (pre<<1) + 1; 	// 最后一层的节点个数
	int lsiz = pre-1,rsiz = pre-1;	// 左子树和右子树不算最后一层的节点数量
	if(last > pre) lsiz += pre, rsiz += last-pre;
	else lsiz += last;		// 算上最后一层的节点数量
	ls[u] = build(l,l+lsiz-1);
	if(rsiz) rs[u] = build(r-rsiz,r-1);	// 递归建树
	return u;	// 返回当前节点的序号
}
inline void bfs(int st){	// 层序遍历
	queue<int> qu; qu.push(st);	
	int siz = 1;
	while(!qu.empty()){		
		int cnt = 0;
		for(int i=1;i<=siz;i++){
			int u = qu.front(); qu.pop();
			if(u != st) printf(" ");
			printf("%d",val[u]);
			if(ls[u]) qu.push(ls[u]),++cnt;
			if(rs[u]) qu.push(rs[u]),++cnt;
		}
		siz = cnt;
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	int rt = build(1,n);
	bfs(rt);
	return 0;
}

L2-4 势均力敌

题目描述

在这里插入图片描述

输入格式

输入第一行给出正整数 n n n 2 < n ≤ 4 2\lt n\le 4 2<n4),随后一行给出 n n n 个不同的、在区间 [ 1 , 9 ] [1, 9] [1,9] 内的个位数字,其间以空格分隔。

输出格式

将所有组成的 n ! n! n! 个不同的 n n n 位数分为平方和相等、且个数也相等的两组。但你只需要输出其中一组就可以了。每个数字占一行,共输出 n ! / 2 n!/2 n!/2 行。
注意:解可能不唯一,输出任何一组解就可以。

样例输入
3
5 2 1
样例输出
125
512
251

解题思路
  • 注意到 n ≤ 4 n\le 4 n4 时,最多只有 4 ! = 24 4!=24 4!=24 个不同的数字。

  • 那么我们可以将这 n ! n! n! 个数字全部求出来,然后依次枚举每个数字取还是不取。

    当取了 n ! / 2 n!/2 n!/2 个数字的时候,计算取了的数字的平方和 m m m ,与提前算好的所有数字的平方和 s s s 相比。

    m ∗ 2 = = s m*2==s m2==s 则找到了一组可行解,输出当前取的这 n ! / 2 n!/2 n!/2 个数字,随后直接结束程序。

完整代码
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N = 30;
int m,n,a[N],val[N]; bool vis[N];
// 将所有可能的数字求出,存在 val[] 中
inline void make(int pos,int s,int k){	// 正在取第pos位,已经选好的所有数字,当前位需要乘的权值
	if(pos == m+1) return val[++n] = s,void();
	for(int i=1;i<=m;i++)
		if(!vis[i]){
			vis[i] = true;
			make(pos+1,s+k*a[i],k*10);
			vis[i] = false;
		}
}
ll tot,ans[N]; bool vs[N];
// 暴力枚举每一个数字取不取
inline bool dfs(int pos,ll sum,int have){	// 正在取第pos位,已经取的平方和是sum,已经取了have个数字
	if(have == n/2){	// 取够数字了
		if(sum*2!=tot) return false;	// 检查平方和的两倍是否等于总平方和
		for(int i=1;i<=have;i++) printf("%d\n",ans[i]);
		return true;
	}
	// 若当前取得数字平方和的两倍已经大于了总平方和,或剩余数字不够取满一半
	if(sum*2 > tot || n-pos+1+have < n/2) return false;	// 直接剪枝	
	ans[have+1] = val[pos];	
	if(dfs(pos+1,sum+val[pos]*val[pos],have+1)) return true;	// 取
	return dfs(pos+1,sum,have);		// 不取
}
inline bool cmp(int x,int y){ return x > y;}
int main(){
	scanf("%d",&m);
	for(int i=1;i<=m;i++) scanf("%d",&a[i]);
	make(1,0,1);
	sort(val+1,val+1+n,cmp);	// 将数字从大到小排序,先取大的数字,更容易触发剪枝
	for(int i=1;i<=n;i++) tot += val[i]*val[i];
	dfs(1,0,0);
	return 0;
}

L3题解

L3-1 City 不 City

题目描述

在这里插入图片描述

输入格式

输入第一行首先给出 4 4 4 个正整数: n n n m m m 1 < n ≤ 1 0 3 1\lt n\le 10^3 1<n103 1 ≤ m ≤ 5 ∗ n 1\le m \le 5*n 1m5n),依次为城镇数量(于是城镇编号从 1 1 1 n n n)和城镇间的通路条数; s s s t t t 依次为旅行者的出发地和目的地的城镇编号。
随后一行给出 n n n 个不超过 100 100 100 的正整数,依次为 n n n 个城镇的旅游热度。
再后面是 m m m 行,每行给出一条通路连接的两个城镇的编号、这条通路的最小花销(其数值为不超过 103 103 103 的正整数)。通路是双向的,题目保证任一对城镇间至多给出一条通路。
同一行的数字间均以空格分隔。

输出格式

题目要求从 s s s t t t 的最小花销路线;若这样的路线不唯一,则取 途径 城镇的最高旅游热度值最小的那条路线。
在一行中输出从 s s s t t t 的最小花销、以及途经城镇的最高旅游热度值(若没有途经的城镇,则热度值为 0 0 0)。数值间以 1 个空格分隔,行首尾不得有多余空格。
若从 s s s 根本走不到 t t t,则在一行中输出 Impossible

样例输入 1
8 14 7 8
100 20 30 10 50 80 100 100
7 1 1
7 2 2
7 3 1
7 4 2
1 2 1
1 5 2
2 5 1
3 4 1
3 5 3
3 6 2
4 6 1
5 6 1
5 8 1
6 8 2
样例输出 1
4 50
样例说明

7 7 7 8 8 8 的最短路径有 3 3 3 条,其中 2 2 2 条都经过城镇 1 1 1,于是对应的最高旅游热度值是城镇 1 1 1 的热度值 100 100 100。解路径为 7 7 7 -> 2 2 2 -> 5 5 5 -> 8 8 8,途径城镇 2 2 2 5 5 5,对应的最高旅游热度值是城镇 5 5 5 的热度值 50 50 50。在最短路径长度相等的情况下,取热度值小的解,故输出的热度值为 50 50 50

样例输入 2
3 1 1 2
10 20 30
1 3 1
样例输出 2
Impossible
解题思路:
  • 题目要求求出距离最短时热度最低的路径,在 Dijkstra 的模板上改改,除了记录 dist[] 表示到每个点的最短距离,再开一个数组 up[] 记录最短路径上的最大热度值。
  • 每次更新的时候,当最短距离相同时,如果最大热度值更小,则也需要更新路径,修改 up 数组并将热度值更小的当前路径入队。
完整代码
#include<iostream>
#include<vector>
#include<queue>
#define ll long long
using namespace std;
const int N = 1e3+5,M = 1e4+5,INF = 0x7fffffff;
int n,m,ha[N],idx,val[N],st,ed;
struct Edge{int from,to,ne,w;}edge[M];
inline void ins(int u,int v,int w){		// 链式前向星存图
	edge[++idx].from = u,edge[idx].to = v,edge[idx].w = w;
	edge[idx].ne = ha[u], ha[u] = idx;
}
struct Node{
	int id; ll dis; int mx; 
	// 按距离排序从小到大排序,距离相同按热度从小到大排序
	const bool operator > (const Node &y) const{	
		return dis == y.dis ? mx > y.mx : dis > y.dis;
	}
};
ll dist[N]; int up[N]; bool vis[N];	// 分别为 最短距离,最短路上的最大热度值,出队标记
priority_queue<Node,vector<Node>,greater<Node> > qu;
inline void dijkstra(){
	for(int i=1;i<=n;i++) dist[i] = INF;
	qu.push({st,0,0}); 
	while(!qu.empty()){
		Node cur = qu.top(); qu.pop();
		int u = cur.id, dis = cur.dis;
		if(vis[u]) continue;
		vis[u] = true;
		for(int i=ha[u];i;i=edge[i].ne){
			int v = edge[i].to,w = edge[i].w;
			int mx = max(val[v],cur.mx);	// 与当前节点的热度值取 max
			if(dist[v] > dis + w){	// 距离更短
				dist[v] = dis + w;
				up[v] = mx;
				qu.push({v,dist[v],up[v]});
			}else if(dist[v] == dis + w && mx < up[v]){	// 距离相同 热度更小
				up[v] = mx;
				qu.push({v,dist[v],up[v]});
			}
		}
	}
}
int main(){
	scanf("%d%d%d%d",&n,&m,&st,&ed);
	for(int i=1;i<=n;i++) scanf("%d",&val[i]);
	val[st] = val[ed] = 0;	// 只算途径的城市的热度值
	for(int i=1,u,v,w;i<=m;i++) scanf("%d%d%d",&u,&v,&w),ins(u,v,w),ins(v,u,w);
	dijkstra();
	if(dist[ed] == INF) printf("Impossible");	// 不可达
	else printf("%lld %d",dist[ed],up[ed]);
	return 0;
}

L3-2 逆序对

题目描述

在这里插入图片描述

输入格式

输入首先在一行中给出一个正整数 n n n l e 1 0 3 le 10^3 le103),随后一行给出 1 1 1 n n n 的一个排列 { a 0 , . . . , a n − 1 a_0,...,a_{n−1} a0,...,an1 }。数字间以空格分隔。

输出格式

对于每一对下标 0 ≤ i ≤ j < n 0\le i\le j\lt n 0ij<n,计算逆转了从 a i a_i ai a j a_j aj 的元素后,序列中逆序对的个数。在一行中输出这 n ∗ ( n + 1 ) / 2 n*(n+1)/2 n(n+1)/2 个整数,按照 i i i 优先的顺序。即:前 n n n 个数字对应逆转了 { a 0 , a 0 a_0,a_0 a0,a0 }、{ a 0 , a 1 a_0,a_1 a0,a1 }、……、{ a 0 , . . . , a n − 1 a_0,... ,a_{n−1} a0,...,an1 } 的结果;下面 n − 1 n−1 n1 个数字对应逆转了 { a 1 , a 1 a_1,a_1 a1,a1 }、{ a 1 , a 2 a_1,a_2 a1,a2 }、……、{ a 1 , . . . , a n − 1 a_1,...,a_{n−1} a1,...,an1 } 的结果;以此类推。
一行中的数字间以 1 1 1 个空格分隔,行首尾不得有多余空格。

输入样例
3
2 1 3
输出样例
1 0 2 1 2 1
解题思路
  • 首先观察到数据范围 n ≤ 1 0 3 n\le 10^3 n103,肯定不能真的将这 n ∗ ( n + 1 ) / 2 n*(n+1)/2 n(n+1)/2 个区间一个一个翻转,然后分别求此时的逆序对。我们需要考虑一个区间 [ l , r ] [l,r] [l,r] 翻转后,相比翻转前逆序对的数量的变化。

  • 我们将区间 [ l , r ] [l,r] [l,r] 看作一个整体 x x x,则 x x x 内部序列翻转带来的变化对于 [ 0 , x − 1 ] [0,x-1] [0,x1] [ x + 1 , n ) [x+1,n) [x+1,n) 是没有任何影响的。所以对于 x x x 左侧和 x x x 右侧的数字,他们对于整个排列中的逆序对数量贡献不变。

  • 那么我们只需要考虑 [ l , r ] [l,r] [l,r] 翻转后内部的逆序对数量变化:

    注意到对于一个区间 [ l , r ] [l,r] [l,r] 内的有序数对:要么是逆序对,要么是正序对。并且这两种数对的数量相加一定为 C n 2 C_n^2 Cn2

    所以我们将 [ l , r ] [l,r] [l,r] 翻转后原先的 逆序对 变成了 正序对,正序对 变成了 逆序对。

    假设原先的逆序对数量为 x x x,翻转后的逆序对数量就为 C n 2 − x C_n^2-x Cn2x 可以直接求得。

  • 因此,我们需要预处理出任意一个区间 [ l , r ] [l,r] [l,r] 的逆序对数量,便可以在 O ( 1 ) O(1) O(1) 的时间内求出任意一个区间翻转后整个序列的逆序对数量了。

  • 我们用 cnt[i][j] 记录区间 [ i , j ] [i,j] [i,j] 内的逆序对数量,很明显 c n t [ i ] [ j ] = c n t [ i ] [ j − 1 ] + 第 j 个数贡献的逆序对数量 cnt[i][j] = cnt[i][j-1] + 第j个数贡献的逆序对数量 cnt[i][j]=cnt[i][j1]+j个数贡献的逆序对数量

    那么我们可以先枚举区间的左端点 i i i,依次插入 a [ j ] a[j] a[j] 到权值树状数组中。对于第 j 个数贡献的逆序对数量即为大于 j 的数字的个数。

    于是我们就可以在 O ( n 2 l o g   n ) O(n^2log\space n) O(n2log n) 的时间内求出任意一个区间 的逆序对数量了。

  • 最后再枚举每个区间,计算翻转 [ i , j ] [i,j] [i,j] 时的逆序对数量:

    [ 0 , j ] [0,j] [0,j] 的数字对于 [ 0 , i − 1 ] [0,i-1] [0,i1] 的数字的总逆序对数量为 cnt[0][j] - cnt[i][j]

    [ j + 1 , n ) [j+1,n) [j+1,n) 的数字对于 [ 0 , j ] [0,j] [0,j] 的数字的总逆序对数量为 cnt[0][n-1] - cnt[0][j]

    m = j-i+1 [ i , j ] [i,j] [i,j] 内部翻转后的总逆序对数量为 m*(m-1)/2 - cnt[i][j]

  • 故每个区间的总逆序对数为:cnt[0][j] - cnt[i][j] + cnt[0][n-1] - cnt[0][j] + m*(m-1)/2 - cnt[i][j],注意输出格式即可。

完整代码
#include<iostream>
using namespace std;
const int N = 1e3+5;
int n,a[N],tr[N];
inline void add(int x,int v){
	for(int i=x;i<=n;i+=i&-i) tr[i]+=v;
}
inline int sum(int x){
	int ans = 0;
	for(int i=x;i>0;i&=i-1) ans+=tr[i];
	return ans;
}
int cnt[N][N];
int main(){ 
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&a[i]);
	for(int i=0;i<n;i++){ 
		for(int j=i;j<n;j++){
			cnt[i][j] = cnt[i][j-1] + j-i - sum(a[j]); // 加上大于 a[j] 的数字个数
			add(a[j],1);		// 插入树状数组
		} 
		for(int k=0;k<N;k++) tr[k] = 0;
	} 
	for(int i=0;i<n;i++){
		for(int j=i;j<n;j++){
			int m = j-i+1;		// 区间长度
			int ans = cnt[0][j] - cnt[i][j] + cnt[0][n-1] - cnt[0][j] + m*(m-1)/2 - cnt[i][j];	// 逆序对数量
			if(i || j) printf(" ");		// 注意输出格式
			printf("%d",ans);
		}
	}
	return 0;
}

L3-3 攀岩 赛时解题思路(无思考量26/30)

声明
  • 本题解非正解,旨在分享赛时思路,同时恳请大佬指点本题标算。
题目描述

简化后:

  • 依次给出平面上 n n n 个点,找到一个最小的 r r r,使得存在某个序列 { a i a_i ai} 满足, a 1 = 1 a_1 = 1 a1=1 a 2 = 2 a_2=2 a2=2 a n = n a_n=n an=n 且对任意的 i ≥ 3 i\ge 3 i3 有: a i − 2 , a i − 1 , a i a_{i-2},a_{i-1},a_i ai2,ai1,ai 三点可以被某个半径为 r r r 的圆覆盖。

原题面:

在这里插入图片描述

A

在这里插入图片描述

输入格式

第一行输入一个整数 t t t ( t ≤ 100 t\le100 t100),表示攀岩馆内攀岩任务的数量。
每个任务的第一行都是一个整数 n n n ( 3 ≤ n ≤ 1500 3\le n\le1500 3n1500),表示攀岩任务涉及的岩点数量。
接下来 n n n 行,每行两个整数 ( x i , y i x_i,y_i xi,yi),表示第 i i i 个岩点的坐标。输入保证 0 ≤ x i , y i ≤ 1 0 6 0\le x_i,y_i\le 10^6 0xi,yi106 且同一个攀岩任务中的岩点坐标两两不同。
输入保证满足 n > 100 n\gt 100 n>100 的数据不超过 3 3 3 组。

输出格式

对于每个攀岩任务输出一行一个浮点数,表示最短可能的机械臂长度。

你的答案在绝对误差或者相对误差不超过 1 0 − 6 10^{−6} 106 的情况下都会被视为正确。

输入样例
4
4
0 0
1 0
0 1
1 2
4
0 0
2 0
0 3
2 2
7
0 0
1 0
2 0
2 1
2 2
1 2
0 2
15
13 2
13 3
12 44
6 17
4 71
14 58
4 49
2 51
8 37
1 18
5 43
5 35
1 84
10 23
13 69
输出样例
0.99999999498
1.41421355524
1.00000000498
10.13227667717

样例解释

在这里插入图片描述

在这里插入图片描述

解题思路( 26 / 30 26/30 26/30

首先我们需要解决如何判断三个点可以被某个半径为 r r r 的圆覆盖:

  • 我们可以先将三个点间的三条边中的最长边作为直径,此时的半径记为 m m m,若 m ≤ r m\le r mr m m m 是足以覆盖这两个最远点的最小值,圆心为最长边的中点。

    若此时剩余的那个点与圆心的距离 ≤ r \le r r(注意是 r r r),则可以被半径为 r r r 的圆覆盖。

  • 若刚才的最小值 m m m 不满足,则考虑给定的三个点构成的三角形的外接圆。外接圆的圆心到三个顶点距离相等且最短,所以我们只需要判断此时的半径若 m ′ ≤ r m' \le r mr 就可以被覆盖。

题目要求求最小的半径 r r r,我们可以转化为二分这个半径 r r r,每次只需要检测对于当前的 r ′ r' r,是否存在一个序列满足题目要求。

随后观察数据乍一看 n ≤ 1500 n\le 1500 n1500 暴力明显超时,但题目表明 n > 100 n\gt 100 n>100 的数据不超过三组,那么我们可以直接无脑暴力,得分期望很高。

  • 选用 b f s bfs bfs 暴力方便优化,我们对于当前点对 { p , q p,q p,q },可以到达的点 i i i 需满足: p , q , i p,q,i p,q,i 三点可以被半径为 r r r 的圆覆盖。
  • 我们从初始点对 { 1 , 2 1,2 1,2 } 开始,每次暴力枚举下一个点 i i i,若可以被覆盖则将点对 { 1 , i 1,i 1,i } 和 { 2 , i 2,i 2,i } 入队,使下一次可以分别从 { 1 , i 1,i 1,i } 和 { 2 , i 2,i 2,i } 出发寻找可能解。
  • 那么总共有 n 2 n^2 n2 个状态,每次拓展需要枚举 n n n 次,总复杂度 O ( n 3 ) O(n^3) O(n3)

接着我们可以进行一些朴素的优化:

  • 对于当前点 { p , q p,q p,q },若可以直接到点 n n n,可以直接返回 true
  • 使用优先队列。每次取编号最大的节点进行拓展。可能到达编号为 n n n 的速度更快。
  • 提前将节点排序。使得在枚举可能的节点时,先枚举距离编号为 n n n 的节点距离更近的节点。
  • 不走回头路。当前点对 { p , q p,q p,q } 拓展结束后,后续节点都不能再选 p p p q q q 进行拓展。

然后随便跑一下就 ( 26 / 30 ) (26/30) (26/30) 了。

完整代码
#include<iostream>
#include<queue>
#include<math.h>
using namespace std;
const int N = 1505; const double esp = 1e-7;
int n;
struct Node{ int x,y; }a[N];
inline double len(const Node &x,const Node &y){
	return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));
}
struct Info{ 
	int p,q; Info(int p=0,int q=0):p(p),q(q){} 	
	bool operator > (const Info &y)const{
		return q > y.q;		// 编号大的优先
	}
};	
// 返回半径 m 的圆能否覆盖三个点
inline bool check(Node &p,Node &q,Node &t,double m){ 
	double x1 = p.x,y1 = p.y,x2 = q.x,y2 = q.y,x3 = t.x,y3 = t.y;
	double R,X,Y,dis;
	double r1 = sqrt((x3-x2)*(x3-x2) + (y3-y2)*(y3-y2));
	double r2 = sqrt((x3-x1)*(x3-x1) + (y3-y1)*(y3-y1));
	double r3 = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
	// 最远的两个点作直径, dis 为剩下一个点与圆心的距离
	if(r1 >= r2 && r1 >= r3){
		X = (x2+x3)/2, Y = (y2+y3)/2, R = sqrt((X-x2)*(X-x2) + (Y-y2)*(Y-y2));
		dis = sqrt((X-x1)*(X-x1) + (Y-y1)*(Y-y1));
	}else if(r2 >= r1 && r2 >= r3){
		X = (x1+x3)/2, Y = (y1+y3)/2, R = sqrt((X-x1)*(X-x1) + (Y-y1)*(Y-y1));
		dis = sqrt((X-x2)*(X-x2) + (Y-y2)*(Y-y2));
	}else if(r3 >= r1 && r3 >= r2){
		X = (x1+x2)/2, Y = (y1+y2)/2, R = sqrt((X-x1)*(X-x1) + (Y-y1)*(Y-y1));
		dis = sqrt((X-x3)*(X-x3) + (Y-y3)*(Y-y3));
	}
	if(R > m) return false;
	if(dis <= m) return true;	// 只要小于 m 即可
	double A,B,C,D;
	A = x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2;
	B = (x1*x1 + y1*y1)*(y3-y2) + (x2*x2 + y2*y2)*(y1-y3) + (x3*x3 + y3*y3)*(y2-y1);
	C = (x1*x1 + y1*y1)*(x2-x3) + (x2*x2 + y2*y2)*(x3-x1) + (x3*x3 + y3*y3)*(x1-x2);
	D = (x1*x1 + y1*y1)*(x3*y2 - x2*y3) + (x2*x2 + y2*y2)*(x1*y3 - x3*y1) + (x3*x3 + y3*y3)*(x2*y1 - x1*y2);
	if(!A) return false;
	X = -B/(2*A), Y = -C/(2*A);
	R = sqrt((B*B + C*C - 4*A*D)/(4*A*A));	// 计算外接圆半径
	return R <= m;
} 
priority_queue<Info,vector<Info>,greater<Info> > qu;
bool vis[N][N],vis2[N]; 
inline bool check(double m){ 
	for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) vis[i][j] = false;
	for(int i=0;i<=n;i++) vis2[i] = false;
	while(!qu.empty()) qu.pop();
	
	qu.push(Info(1,2)); vis[1][2] = vis[2][1] = true;
	while(!qu.empty()){ 
		Info cur = qu.top(); qu.pop();
		int p = cur.p, q = cur.q;
		if(check(a[p],a[q],a[n],m)) return true;
		for(int i=1;i<n;i++){ 
			if(i == p || i == q || vis2[i]) continue;
			if(!check(a[p],a[q],a[i],m)) continue;
			if(!vis[q][i])  vis[q][i] = vis[i][q] = true, qu.push(Info(q,i));
			if(!vis[p][i])  vis[p][i] = vis[i][p] = true, qu.push(Info(p,i));
		} 
		vis2[p] = vis2[q] = true;
	} 
	return false;
} 
inline bool cmp(const Node &x,const Node &y){
	return len(x,a[n]) < len(y,a[n]);
}
inline void solve(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y);
	sort(a+3,a+n,cmp);		// 与终点距离短的优先
	double l = 0, r = 800000, mid, res;	// 10^6 / 2^0.5
	while(l+esp <= r){
		mid = (l+r)/2;
		if(check(mid)) r = mid-esp, res = mid;
		else l = mid+esp;
	}
	printf("%lf\n",res);
}
int main(){
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}
补充
  • 后续尝试了加上 双端 BFS,卡常,预处理,仍然暴力无法过两个大数据。
  • 再次望大佬分享本题正解。

完结撒花

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值