Acwing 第 157 场周赛题解(ABC)
补一下Acwing第157场周赛,本场与传统一样easy+mid+hard各一道,话不多说,开始上题解
目录
A题:AcWing 5838. 四舍五入
题面描述
给定一个两位正整数 n,请你输出其四舍五入到十位后的结果。
输入格式
一个正整数 n。
输出格式
一个整数,表示 n 四舍五入到十位后的结果。
数据范围
所有测试点满足 10≤n≤99
输入样例1:
13
输出样例1:
10
输入样例2:
98
输出样例2:
100
思路分析
签到题,从样例中我们就能很容易发现输入n后,n再对10取模进行判断
- 大于等于5表示要往更大的数入,于是n本身减去取模数再加上10即可
- 小于5表示要往小的数舍,于是n本身只减去取模数即可
代码实现
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
void solve()
{
int n;
cin>>n;
if(n % 10 >= 5) n = n - n % 10 + 10;
else n = n - n % 10;
cout<<n<<endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int t = 1;
while(t--) solve();
return 0;
}
B题:AcWing 5839. 四舍五入II
题面描述
给定一个实数,保证该数为正数,其小数点后至少包含一位非零的数字,且小数点后不含多余末尾 0。
如果将该实数转化为字符串,则字符串的长度为 n。
现在,你可以对该实数进行不超过 t 次(也可以零次)四舍五入操作。
每次操作可以将当前数四舍五入到小数点后的任意位置(也可以四舍五入到最接近的整数)。
例如,1.645 四舍五入到小数点后两位可以得到 1.65,1.141 四舍五入到小数点后一位可以得到 1.1,3.762 四舍五入到最接近的整数可以得到 4。
我们希望通过四舍五入操作,将给定实数变得尽可能大。
请你输出通过上述操作可以得到的最大可能值。
输入格式
第一行包含两个整数 n,t。
第二行包含给定正实数,保证其既包含整数部分也包含小数部分,且小数点后至少包含一位非零的数字,且小数点后不含多余末尾 0。如果将该实数转化为字符串,则字符串的长度为 n。
输出格式
输出一个实数,表示可以得到的最大可能值。
输出答案不应包含小数点后多余末尾 0。
如果答案是整数,则直接输出整数。
数据范围
前 4 个测试点满足 1≤n≤12,1≤t≤100。
所有测试点满足 1≤n≤2×10^5,1≤t≤10^9。
输入样例1:
6 1
10.245
输出样例1:
10.25
输入样例2:
6 2
10.245
输出样例2:
10.3
输入样例3:
3 100
9.2
输出样例3:
9.2
思路分析
tag:贪心+模拟 O(n)时间复杂度
- 整数部分:不会四舍五入,但会有进位的情况
- 小数部分:从前往后找到第一个可以四舍五入的地方(即>=5)开始从后往前推
- 因为越靠近小数点四舍五入后就越大,之后从后往前推是因为会有四舍五入后前一位>=5的情况。
本题要注意太多边界情况,少判断一个都会WA掉,因此一定要想清楚所有边界条件,已在代码中详细注释 --------
代码实现
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
void solve()
{
int n,m; cin>>n>>m;
string s; cin>>s;
int dot = s.find('.');//找到小数点的位置
int k = dot + 1;
while(k<s.size()&&s[k]<'5') k++;//找到第一个大于5的地方,然后往前枚举位置
if(k==s.size()) cout<<s<<endl;
//如果遍历完k等于s的长度表示小数部分都小于5直接输出,表示不能进位
else
{
s = s.substr(0,k);
//先截取这样可以避免后面不好截了,如样例1
//从0的位置截取长度为k的字符串,刚好可以截到k上一个
k--;m--;//次数位置都减1
while(k>=0)
{
if(s[k]!='.')
{
s[k]++;
if(s[k]<'5') break;//小于5表示不能往前进位了
//大于等于5小于等于9表示还能进,但是要注意给的次数或者在整数部分就不能进了
if(s[k] <= '9')
{
if(!m||k<dot) break;
s[k] = '0';
m--;
}
else s[k]='0';//要是在整数部分加一之后是10就直接变成0
/* 如样例7 1000000000
239.923*/
}
k--;
}
if(k<0) s = "1"+ s; //例如999.5进位为1000
while(s.back()=='0') s.pop_back();//去除末尾0,如样例二不去除就是10.30
if(s.back()=='.') s.pop_back();//如999.5,不去除就是999.
cout<<s<<endl;
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int t = 1;
while(t--) solve();
return 0;
}
C题:AcWing 5840. 封印序列
题面描述
给定一个长度为 n 的非负整数序列 a1,a2,…,an。
其中的所有元素将被逐个封印。
具体封印顺序可以用一个 1∼n 的排列 b1,b2,…,bn 来描述,第 i 个被封印的元素即为abi。
你需要完成 n 个任务(编号 1∼n),其中第 i 个任务是:对于完成前 i 次封印的序列,请你找到序列中的一个连续子序列(可以为空),使得该子序列不含任何被封印的元素,且子序列内各元素之和尽可能大,输出这个子序列元素和的最大可能值。
空序列的元素和视为 0。
输入格式
第一行包含整数 n。
第二行包含 a1,a2,…,an。
第三行包含 b1,b2,…,bn。
输出格式
共 n 行,第 i 行输出第 i 个任务的结果。
数据范围
前 4 个测试点满足 1≤n≤10。
所有测试点满足 1≤n≤10^5,0≤ai≤10^9,b1∼bn 是一个 1∼n 的排列。
输入样例1:
4
1 3 2 5
3 4 1 2
输出样例1:
5
4
3
0
输入样例2:
5
1 2 3 4 5
4 2 3 5 1
输出样例2:
6
5
5
1
0
输入样例3:
8
5 5 4 4 6 6 5 5
5 2 8 7 1 3 4 6
输出样例3:
18
16
11
8
8
6
6
0
思路分析
笔者看到这题的最好方法是序列化并查集,可惜还不会这个,之后去补一下这个tag,先贴个序列化并查集的模版题在这——疯狂的馒头,但这题一样可以用线段树来做,只需会套线段树的模版就行,关于线段树可以移步很多帖子或者视频学习,就不细说了,也贴一个本题很像的线段树tag原题 ——你能回答这些问题吗 ,都是经典的好题,明白一题做这个问题应该就不大了,下面分别给出线段树和并查集的解法——
代码实现
1.线段树
#include<iostream>
#include<cstdio>
#include<algorithm>
#define int long long
#define x first
#define y second
const int N = 1e5+10;
using namespace std;
int w[N],b[N];
struct node{
int l,r;
int sum,lmax,rmax,tmax;
}tr[N * 4];
void pushup(node &u,node &l,node &r)
{
u.sum = l.sum + r.sum;
u.lmax = max(l.lmax,l.sum + r.lmax);
u.rmax = max(r.rmax,r.sum + l.rmax);
u.tmax = max(l.rmax + r.lmax,max(l.tmax,r.tmax));
}
void pushup(int u)
{
pushup(tr[u],tr[u << 1],tr[u << 1 | 1]);
}
void build(int u,int l,int r)
{
if(l == r) tr[u] = {l,r,w[r],w[r],w[r],w[r]};
else
{
tr[u] = {l, r}; // 设当前节点区间为[l, r]
int mid = l + r >> 1;
build(u << 1, l, mid); // 建立左子树
build(u << 1 | 1, mid + 1, r); // 建立右子树
pushup(u); // 修改父节点
}
}
void modify(int u, int x, int v) {
if (tr[u].l == x && tr[u].r == x) tr[u] = {x, x, v, v, v, v}; // 找到了
else {
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, v); // x位于当前区间的左半子区间
else modify(u << 1 | 1, x, v); // x位于当前区间的右半子区间
pushup(u); // 修改父节点的相关信息
}
}
node query(int u, int l, int r) { // 从节点u开始,查找区间[l, r]的信息
// 1. 包含在区间内
// Tl-----Tr
// L-------------R
if (tr[u].l >= l && tr[u].r <= r) return tr[u];
int mid = tr[u].l + tr[u].r >> 1;
// 2. 在当前的左半区间
// Tl-----m-----Tr
// L---R
if (r <= mid) return query(u << 1, l, r);
// 3. 在当前的右半区间
// Tl-----m-----Tr
// L-----R
else if (l > mid) return query(u << 1 | 1, l, r);
// 4. 两边都有,都查询
// Tl----m----Tr
// L-----R
else {
auto left = query(u << 1, l, r);
auto right = query(u << 1 | 1, l, r);
node res;
// 合并答案
pushup(res, left, right);
return res;
}
}
void solve()
{
int n; cin>>n;
for(int i = 1; i <= n; i++) cin>>w[i];
build(1,1,n);
for(int i = 1;i <= n; i++)
{
cin >> b[i];
modify(1,b[i],-1e14);
cout<<max(0ll,query(1,1,n).tmax)<<endl;
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int t = 1;
while(t--) solve();
return 0;
}
2.并查集
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n;
int a[N], b[N], p[N];
bool st[N];
LL s[N], ans[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ ) scanf("%d", &b[i]);
for (int i = 1; i <= n; i ++ ) p[i] = i;
for (int i = n; i; i -- )
{
int k = b[i];
st[k] = true, s[k] = a[k];
if (st[k + 1]) s[find(k + 1)] += s[k], p[k] = find(k + 1);
if (st[k - 1]) s[find(k)] += s[k - 1], p[k - 1] = find(k);
ans[i - 1] = max(ans[i], s[find(k)]);
}
for (int i = 1; i <= n; i ++ ) printf("%lld\n", ans[i]);
return 0;
}
好了,本篇题解分享就到这里了,下期见~~~~

浙公网安备 33010602011771号