2025 天梯赛 PTA 模拟赛 题解答案 L1+L2+L3
旨在分享赛时的解题思路,恳请大佬指点优化
2025 天梯赛 PTA 模拟赛 题解答案 L1+L2+L3
声明
- 不保证为官方标算,若有更优解法恳请大佬指点。
- 图片来自 PTA 习题集。
L1题解
L1-1 爱您爱我
题目描述
202520252025 谐音是 “爱您爱我”。本题就请你用汉语拼音输出这句话 2025 - ai nin ai wo
。
解题思路
- 直接输出即可。
完整代码
#include<iostream>
using namespace std;
int main(){
cout << "2025 - ai nin ai wo";
return 0;
}
L1-2 九牛一毛
题目描述
输入格式
输入在一行中给出一个不超过 100010001000 的正整数 NNN,即以 “元” 为单位的货币量。
输出格式
在一行中顺序输出 NNN 块钱能买多少斤猪肉、多少斤鸡肉、多少头牛。三个数字都只取整数部分,其间以 111 个空格分隔,行首尾不得有多余空格。
解题思路
- 按题意直接计算即可。
完整代码
#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 陈依涵的高情商体重计算器
题目描述
输入格式
输入第一行给出一个用户输入的体重值 www,以斤为单位,是一个不超过 500500500 的正整数。
输出格式
在一行中输出: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
解题思路
- 依题意,大于 100100100 斤输出 100100100 斤,否则输出 w−1w-1w−1 后抹去个位数即可。
完整代码
#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 记小本本
题目描述
输入格式
输入由一系列 000 和 111 组成,每个数字占一行。111 代表红色按钮被按下,000 代表绿色按钮被按下。当出现任何一个既不是 000 也不是 111 的数字时,表示他们把电源线扯断了,输入结束,最后那个数字不要处理。
输出格式
对每一个输入的 000,在一行中输出这次按下绿色按钮之前一共吵了多少次架。
题目保证每个输出的数字均不超过 10410^4104。
样例输入
1
1
1
0
1
1
0
1
2
样例输出
3
5
解题思路
- 依次输入,遇到 111 计数加一,遇到 000 输出当前计数。除此之外直接退出。
完整代码
#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,MN,MN,M (1≤M≤N≤301\le M\le N\le301≤M≤N≤30),表示本次天梯赛有 NNN 道题目,吉老师现在做完了 MMM 道。
接下来 NNN 行,每行是一个符合题目描述的字符串,表示天梯赛的题目内容。吉老师会按照给出的顺序看题——第一行就是吉老师看的第一道题,第二行就是第二道,以此类推。
输出格式
在一行中输出吉老师当前正在做的题目对应的题面(即做完了 MMM 道题目后,吉老师正在做哪个题)。如果吉老师已经把所有他打算做的题目做完了,输出一行 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+1m+1m+1 个不包含
qiandao
和easy
的字符串。若不存在第 m+1m+1m+1 个,则输出
Wo AK le
。 -
那么我们每找到一个满足题意的字符串就将 m−1m-1m−1。在某次找到字符串时,若 m=0m=0m=0 说明此时为第 m+1m+1m+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 大勾股定理
题目描述
输入格式
输入在一行中给出正整数 nnn(≤104\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
解题思路
思路一
- 发现输入一个 nnn,答案一定是连续的 2∗n+12*n+12∗n+1 个正整数,前 n+1n+1n+1 个的平方和等于后 nnn 个的平方和。
- 那么我们可以用一个 滑动窗口,窗口长度为 2∗n+12*n+12∗n+1。每次窗口向右移一位时,等式左边的平方和只需要减去出窗口的那个数的平方,再新加上进窗口的数的平方。等式右边的平方和同理。
- 直到某刻等式左边的平方和 等于 等式右边的平方和,此时窗口内的 2∗n+12*n+12∗n+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...10n=1,2,3...10n=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∗(2∗n+1)。所以从 n∗(2∗n+1)n*(2*n+1)n∗(2∗n+1) 开始的连续的 2∗n+12*n+12∗n+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 个整数,依次为 a1a_1a1、a2a_2a2 和 nnn,满足 0≤a1,a2≤90\le a_1,a_2\le 90≤a1,a2≤9,0<n≤1030\lt n\le 10^30<n≤103。
输出格式
在一行中输出数列的前 nnn 项。数字间以 111 个空格分隔,行首尾不得有多余空格。
样例输入
2 3 10
样例输出
2 3 6 1 8 6 8 4 8 4
解题思路
- 按照题意,先生成 nnn 个数字,然后一次性输出。
- 用一个变量 ppp 指向当前数组的末尾(新数字要插入的位置)。从前往后每次取相邻两个数字,计算二者乘积,若是一位数直接填在末尾,若是两位数则拆开填入末尾两次。
- 当 p>np\gt np>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,HN,M,HN,M,H (1≤N,M≤501\le N,M\le501≤N,M≤50 , 1≤H≤2101\le H\le 2101≤H≤210),表示活动场地矩阵大小为 N×MN×MN×M,小 C 的身高为 HHH。
接下来的 NNN 行,每行 MMM 个整数,整数的含义如下:
- 如果是一个正整数,则表示该格子被人或建筑物占据,高度为对应的值。
- 如果是一个负整数,则表示该格子用于燃放烟花。所有燃放烟花的格子视为没有高度。
- 如果是 000,则表示该格子是空格子。
所有整数的绝对值不超过 500500500。
输出格式
输出第一行是一个正整数,表示对小 C 而言的优秀观赏位置有多少个。
接下来输出能看到最多的燃放烟花的格子的坐标 (X,Y)(X,Y)(X,Y),即格子在第 XXX 行、第 YYY 列,数字间以 111 个空格分隔。当存在多组坐标时,只输出最小的那组,即输出 XXX 最小的解;XXX 相同时输出 YYY 最小的解。
矩阵左上角坐标为 (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
解题思路
-
我们发现 NNN 和 MMM 都很小,不超过 505050,那么可以暴力枚举每一个坐标 (x,y)(x,y)(x,y),若其是空地,则计算从这个坐标可以看到的烟花数量。
-
那么我们只需要统计有多少个位置,看到的烟花数量 ≥3\ge3≥3,并记录一下看到烟花数量最多的位置即可。
-
对于一个位置 (x,y)(x,y)(x,y),我们站在该点,当向一个方向看去时,统计直到第一个高度大于 HHH 的格子之前,出现过多少个烟花,即是这个方向上能看到的烟花数量。
四个方向分别加起来即为该点 (x,y)(x,y)(x,y) 能看到的烟花数量。
-
由于题目要求在看到烟花数量相同时,输出最小的坐标。我们可以按题意先从小到大遍历 XXX,再从小到大遍历 YYY,那么只需要在烟花数量更大时更新即可。
完整代码
#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 完全二叉树的层序遍历
题目描述
输入格式
输入在第一行中给出正整数 NNN(≤30\le30≤30),即树中结点个数。第二行给出后序遍历序列,为 NNN 个不超过 100100100 的正整数。同一行中所有数字都以空格分隔。
输出格式
在一行中输出该树的层序遍历序列。所有数字都以 111 个空格分隔,行首尾不得有多余空格。
样例输入
8
91 71 2 34 10 15 55 18
样例输出
18 34 55 71 2 10 15 91
解题思路
-
由于是一颗完全二叉树,除了最后一层前面每一层都填满了节点。
故我们可以计算出当前节点的左子树的节点个数,以及右子树的节点个数。
-
由后序遍历的性质,左子树的所有节点先出现,然后右子树所有节点出现,最后出现根节点。
-
那么我们递归建树,最后跑一遍 bfsbfsbfs 求出层序遍历即可。
完整代码
#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 势均力敌
题目描述
输入格式
输入第一行给出正整数 nnn(2<n≤42\lt n\le 42<n≤4),随后一行给出 nnn 个不同的、在区间 [1,9][1, 9][1,9] 内的个位数字,其间以空格分隔。
输出格式
将所有组成的 n!n!n! 个不同的 nnn 位数分为平方和相等、且个数也相等的两组。但你只需要输出其中一组就可以了。每个数字占一行,共输出 n!/2n!/2n!/2 行。
注意:解可能不唯一,输出任何一组解就可以。
样例输入
3
5 2 1
样例输出
125
512
251
解题思路
-
注意到 n≤4n\le 4n≤4 时,最多只有 4!=244!=244!=24 个不同的数字。
-
那么我们可以将这 n!n!n! 个数字全部求出来,然后依次枚举每个数字取还是不取。
当取了 n!/2n!/2n!/2 个数字的时候,计算取了的数字的平方和 mmm ,与提前算好的所有数字的平方和 sss 相比。
若 m∗2==sm*2==sm∗2==s 则找到了一组可行解,输出当前取的这 n!/2n!/2n!/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
题目描述
输入格式
输入第一行首先给出 444 个正整数:nnn 和 mmm(1<n≤1031\lt n\le 10^31<n≤103,1≤m≤5∗n1\le m \le 5*n1≤m≤5∗n),依次为城镇数量(于是城镇编号从 111 到 nnn)和城镇间的通路条数;sss 和 ttt 依次为旅行者的出发地和目的地的城镇编号。
随后一行给出 nnn 个不超过 100100100 的正整数,依次为 nnn 个城镇的旅游热度。
再后面是 mmm 行,每行给出一条通路连接的两个城镇的编号、这条通路的最小花销(其数值为不超过 103103103 的正整数)。通路是双向的,题目保证任一对城镇间至多给出一条通路。
同一行的数字间均以空格分隔。
输出格式
题目要求从 sss 到 ttt 的最小花销路线;若这样的路线不唯一,则取 途径 城镇的最高旅游热度值最小的那条路线。
在一行中输出从 sss 到 ttt 的最小花销、以及途经城镇的最高旅游热度值(若没有途经的城镇,则热度值为 000)。数值间以 1 个空格分隔,行首尾不得有多余空格。
若从 sss 根本走不到 ttt,则在一行中输出 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
样例说明
从 777 到 888 的最短路径有 333 条,其中 222 条都经过城镇 111,于是对应的最高旅游热度值是城镇 111 的热度值 100100100。解路径为 777 -> 222 -> 555 -> 888,途径城镇 222 和 555,对应的最高旅游热度值是城镇 555 的热度值 505050。在最短路径长度相等的情况下,取热度值小的解,故输出的热度值为 505050。
样例输入 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 逆序对
题目描述
输入格式
输入首先在一行中给出一个正整数 nnn(le103le 10^3le103),随后一行给出 111 到 nnn 的一个排列 { a0,...,an−1a_0,...,a_{n−1}a0,...,an−1 }。数字间以空格分隔。
输出格式
对于每一对下标 0≤i≤j<n0\le i\le j\lt n0≤i≤j<n,计算逆转了从 aia_iai 到 aja_jaj 的元素后,序列中逆序对的个数。在一行中输出这 n∗(n+1)/2n*(n+1)/2n∗(n+1)/2 个整数,按照 iii 优先的顺序。即:前 nnn 个数字对应逆转了 { a0,a0a_0,a_0a0,a0 }、{ a0,a1a_0,a_1a0,a1 }、……、{ a0,...,an−1a_0,... ,a_{n−1}a0,...,an−1 } 的结果;下面 n−1n−1n−1 个数字对应逆转了 { a1,a1a_1,a_1a1,a1 }、{ a1,a2a_1,a_2a1,a2 }、……、{ a1,...,an−1a_1,...,a_{n−1}a1,...,an−1 } 的结果;以此类推。
一行中的数字间以 111 个空格分隔,行首尾不得有多余空格。
输入样例
3
2 1 3
输出样例
1 0 2 1 2 1
解题思路
-
首先观察到数据范围 n≤103n\le 10^3n≤103,肯定不能真的将这 n∗(n+1)/2n*(n+1)/2n∗(n+1)/2 个区间一个一个翻转,然后分别求此时的逆序对。我们需要考虑一个区间 [l,r][l,r][l,r] 翻转后,相比翻转前逆序对的数量的变化。
-
我们将区间 [l,r][l,r][l,r] 看作一个整体 xxx,则 xxx 内部序列翻转带来的变化对于 [0,x−1][0,x-1][0,x−1] 和 [x+1,n)[x+1,n)[x+1,n) 是没有任何影响的。所以对于 xxx 左侧和 xxx 右侧的数字,他们对于整个排列中的逆序对数量贡献不变。
-
那么我们只需要考虑 [l,r][l,r][l,r] 翻转后内部的逆序对数量变化:
注意到对于一个区间 [l,r][l,r][l,r] 内的有序数对:要么是逆序对,要么是正序对。并且这两种数对的数量相加一定为 Cn2C_n^2Cn2。
所以我们将 [l,r][l,r][l,r] 翻转后原先的 逆序对 变成了 正序对,正序对 变成了 逆序对。
假设原先的逆序对数量为 xxx,翻转后的逆序对数量就为 Cn2−xC_n^2-xCn2−x 可以直接求得。
-
因此,我们需要预处理出任意一个区间 [l,r][l,r][l,r] 内的逆序对数量,便可以在 O(1)O(1)O(1) 的时间内求出任意一个区间翻转后整个序列的逆序对数量了。
-
我们用
cnt[i][j]
记录区间 [i,j][i,j][i,j] 内的逆序对数量,很明显 cnt[i][j]=cnt[i][j−1]+第j个数贡献的逆序对数量cnt[i][j] = cnt[i][j-1] + 第j个数贡献的逆序对数量cnt[i][j]=cnt[i][j−1]+第j个数贡献的逆序对数量。那么我们可以先枚举区间的左端点 iii,依次插入 a[j]a[j]a[j] 到权值树状数组中。对于第 j 个数贡献的逆序对数量即为大于 j 的数字的个数。
于是我们就可以在 O(n2log 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,i−1] 的数字的总逆序对数量为
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)
声明
- 本题解非正解,旨在分享赛时思路,同时恳请大佬指点本题标算。
题目描述
简化后:
- 依次给出平面上 nnn 个点,找到一个最小的 rrr,使得存在某个序列 {aia_iai} 满足,a1=1a_1 = 1a1=1,a2=2a_2=2a2=2,an=na_n=nan=n 且对任意的 i≥3i\ge 3i≥3 有:ai−2,ai−1,aia_{i-2},a_{i-1},a_iai−2,ai−1,ai 三点可以被某个半径为 rrr 的圆覆盖。
原题面:
输入格式
第一行输入一个整数 ttt (t≤100t\le100t≤100),表示攀岩馆内攀岩任务的数量。
每个任务的第一行都是一个整数 nnn (3≤n≤15003\le n\le15003≤n≤1500),表示攀岩任务涉及的岩点数量。
接下来 nnn 行,每行两个整数 (xi,yix_i,y_ixi,yi),表示第 iii 个岩点的坐标。输入保证 0≤xi,yi≤1060\le x_i,y_i\le 10^60≤xi,yi≤106 且同一个攀岩任务中的岩点坐标两两不同。
输入保证满足 n>100n\gt 100n>100 的数据不超过 333 组。
输出格式
对于每个攀岩任务输出一行一个浮点数,表示最短可能的机械臂长度。
你的答案在绝对误差或者相对误差不超过 10−610^{−6}10−6 的情况下都会被视为正确。
输入样例
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/3026/3026/30)
首先我们需要解决如何判断三个点可以被某个半径为 rrr 的圆覆盖:
-
我们可以先将三个点间的三条边中的最长边作为直径,此时的半径记为 mmm,若 m≤rm\le rm≤r 则 mmm 是足以覆盖这两个最远点的最小值,圆心为最长边的中点。
若此时剩余的那个点与圆心的距离 ≤r\le r≤r(注意是 rrr),则可以被半径为 rrr 的圆覆盖。
-
若刚才的最小值 mmm 不满足,则考虑给定的三个点构成的三角形的外接圆。外接圆的圆心到三个顶点距离相等且最短,所以我们只需要判断此时的半径若 m′≤rm' \le rm′≤r 就可以被覆盖。
题目要求求最小的半径 rrr,我们可以转化为二分这个半径 rrr,每次只需要检测对于当前的 r′r'r′,是否存在一个序列满足题目要求。
随后观察数据乍一看 n≤1500n\le 1500n≤1500 暴力明显超时,但题目表明 n>100n\gt 100n>100 的数据不超过三组,那么我们可以直接无脑暴力,得分期望很高。
- 选用 bfsbfsbfs 暴力方便优化,我们对于当前点对 { p,qp,qp,q },可以到达的点 iii 需满足: p,q,ip,q,ip,q,i 三点可以被半径为 rrr 的圆覆盖。
- 我们从初始点对 { 1,21,21,2 } 开始,每次暴力枚举下一个点 iii,若可以被覆盖则将点对 { 1,i1,i1,i } 和 { 2,i2,i2,i } 入队,使下一次可以分别从 { 1,i1,i1,i } 和 { 2,i2,i2,i } 出发寻找可能解。
- 那么总共有 n2n^2n2 个状态,每次拓展需要枚举 nnn 次,总复杂度 O(n3)O(n^3)O(n3)。
接着我们可以进行一些朴素的优化:
- 对于当前点 { p,qp,qp,q },若可以直接到点 nnn,可以直接返回
true
。 - 使用优先队列。每次取编号最大的节点进行拓展。可能到达编号为 nnn 的速度更快。
- 提前将节点排序。使得在枚举可能的节点时,先枚举距离编号为 nnn 的节点距离更近的节点。
- 不走回头路。当前点对 { p,qp,qp,q } 拓展结束后,后续节点都不能再选 ppp 和 qqq 进行拓展。
然后随便跑一下就 (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,卡常,预处理,仍然暴力无法过两个大数据。
- 再次望大佬分享本题正解。
完结撒花
更多推荐
所有评论(0)