sust2024天梯选拔赛题解
天梯选拔赛个人题解
题目照做题顺序书写,大致为难度排列
题目链接:传送门
A
单纯的签到题
回复的邮件的数目为收到的材料数目和ppt数目的总和
因此输出 x + y即可
L
要求算出最多的得分,做出简单题目得10分,中等20,难题30
且根据题目难度,做出题目数目非递增‘
可以讨论,但是数据范围在0-9,即使$O(n^3)$的枚举也不会超时,所以直接枚举即可
void solve()
{
int n;
cin >> n;
ll ans = 0;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= n; j++)
for (int k =0; k <= n; k++){
if(i < j || j < k || i < k || i + j + k > n)
continue;
ll cnt = 10 * i + 20 * j + 30 * k;
ans = cnt > ans ? cnt : ans;
}
cout << ans;
return;
}
H
简要题意:咖啡1元,消费满y元,返现1元。请问x元能喝多少天咖啡
还是先看数据范围2~1e3,直接模拟就能解决
void solve()
{
int x, y;
while(cin>>x>>y && x && y){//判断输入是否合法
ll cnt = 0;//统计喝了多少杯咖啡
while(x > 0){
x--;
cnt++;
if(cnt % y == 0)//。当咖啡数目够y的时候,返现1元
x++;
}
cout << cnt << endl;
}
return;
}
F
原本是一道cf的div 2 D,难度为省选的线段树,但是放宽了时限,使得暴力可以通过。
于是乎,直接按照题目模拟即可。
void solve()
{
int n, m;
cin >> n >> m;
vector<int> v(n + 1);
for (int i = 1; i <= n; i++)
cin >> v[i];
while (m--)
{
int op;
cin >> op;
if (op == 1)
{
ll l, r;
cin >> l >> r;
ll ans = 0;//不开ll可能会爆
for (int i = l; i <= r; i++)
ans += v[i];
cout << ans << endl;
}
else if (op == 2)
{
ll l, r, x;
cin >> l >> r >> x;
for (int i = l; i <= r; i++)
v[i] %= x;
}
else
{
int k, x;
cin >> k >> x;
v[k] = x;
}
}
return;
}
G
这道题实际上是一道经典结论,如果不知道,手推样例也可以guess到(bushi

这个点的位置在所有的点的最中间的点时,所有点到这个点的距离最短
具体证明可以参考这个🔗
但是如果n为偶数时,应该取较大的数字是更优的
按照这个思路,直接写代码就可以了
void solve()
{
int n;
cin >> n;
vector<int> v(n + 1);
for (int i = 1; i <= n; i++)
cin >> v[i];
sort(v.begin() + 1, v.end());
ll pos = n / 2 + 1;//向上取整中位数的位置
//也就是处理偶数时,如何取到更大的数字
ll cnt = 0;
for (int i = 1; i <= n; i++){
if(i == pos)//i等于这个点的时候直接跳过就行
continue;
cnt += abs(v[i] - v[pos]);
}
cout << v[pos] << " " << cnt << endl;
return;
}
I
这个是一道很经典的最大连续子段和问题
不过多进行讲解,浙江大学陈越老师讲的比我好多了,不懂的同学可以直接去看视频
放一个链接🔗在这里
代码如下
void solve()
{
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i];
int flag = 0;
for (int i = 1; i <= n; i++)
{
if (a[i] < 0)
continue;
else
flag++;
}
auto deal = [&](long long n) ->ll//这个是lambda表达式,看不懂的同学把他单独写出去当成一个函数就行
{
long long this_sum = 0;
long long max_sum = 0;
for (int i = 1; i <= n; i++)
{
this_sum += a[i];
if (this_sum > max_sum)
max_sum = this_sum;
if (this_sum < 0)
this_sum = 0;
}
return max_sum;
};
if (flag == 0)
{
cout << *max_element(a.begin() + 1, a.end());
}
else
{
long long ans = deal(n);
cout << ans;
}
return;
}
J
很经典的一道二分,方法和之前学校比赛出过的https://hydro.ac/d/sust/p/3709?tid=649411f212259bc4db64bbf6
记忆面包一模一样
面对这种题目,对l的最大长度进行二分求和即可
void solve()
{
int n, m;
cin >> n >> m;
vector<int> a(n + 1);
ll l = 0, r = 0;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
r = a[i] > r ? a[i] : r;//取右边界的最大值
}
r += 1;//加1为了防止答案就是最大值
//e.g. n = 3,1 1 1,这种情况
auto deal = [&](ll x) -> bool
{
ll sum = 0;
for (int i = 1; i <= n; i++)
{
sum += a[i] / x;//统计每个木头能切成几段
if (sum >= m)
return 1;//假如切成的木头已经大于要求的段数了,就去尝试把他切成更大的段
}
return 0;
};
while (l + 1 < r)
{
ll mid = l + (r - l) / 2;
if (deal(mid))//如果可以,就去判断更大的段是否可以
l = mid;
else//不能,就去尝试切成更小的段
r = mid;
}
cout << l;
return;
}
M
2023年杭州的签到题
简要题意:定义一个先减少后增加的序列为好的序列,给定一个好的序列。求子序列的平均最大值,要求子序列也要是好的。
很简单的贪心思想,求一个子序列的平均最大值,那么元素越大,数目越少就符合。也就是分为三种情况。
1.全选
2.选最小的和上升的
3.选下降的和最小的
对于这三种情况去取最大值就是结果
code
void solve()
{
double n;
cin >> n;
auto v = vector<ll>(n + 1, 1e18);
auto suml = v;
auto sumr = v;
int pos = 0;
for (int i = 1; i <= n; i++)
cin >> v[i], pos = v[i] < v[pos] ? i : pos;
double sum = accumulate(v.begin() + 1, v.end(), 0);
double minsum = 0, maxsum = 0;
for (int i = 1; i <= pos + 1; i++)
minsum += v[i];
for (int i = pos - 1; i <= n; i++)
maxsum += v[i];
double ans = max({1.00 * (minsum / (pos + 1)), 1.00 * (maxsum / (n - pos + 2)), 1.00 * (sum / n)});
cout << setiosflags(ios::fixed) << setprecision(6);
cout << ans;
return;
}
D
序列求环问题,我还在cf上写过一道类似的,但是寻找和问群友都没得出结果。
记忆化搜索和并查集都能够解决这个问题,如果你不会,可以先学习一下这两个算法。
我这里提供并查集+dfs的方法来解决
void solve()
{
int n;
cin >> n;
vector<int> v(n + 1, 0);
auto fa = v;
auto cnt = v;//开一个数组记录环的大小
for (int i = 1; i <= n; i++)
cin >> v[i];
for (int i = 0; i <= n; i++)
fa[i] = i;//初始化并查集
function<int(int)> my_find = [&](int x)
{
return fa[x] == x ? x : fa[x] = my_find(fa[x]);
};//路径压缩
auto merge = [&](int x, int y)
{
fa[my_find(x)] = my_find(y);
};//合并
function<void(int)> dfs = [&](int pos) ->void{
if(my_find(pos) == my_find(v[pos])){//假如这个点已经合并在一起了,也就是之前的循环跑到过这里,就给cnt(这个点的父亲)加一,然后return
cnt[my_find(pos)]++;
return;
}
else
cnt[my_find(pos)]++;//其他情况给这个点的父亲加一
// cerr<< pos << " " << v[pos] << endl;
merge(pos, v[pos]);/合并这个点的下标和指向的点
dfs(v[pos]);//对指向的点进行搜索
return;
};
for (int i = 1; i <= n; i++)
dfs(i);//从每一个点都跑一遍搜索
for (int i = 1; i <= n; i++)
cout << cnt[my_find(v[i])] << " ";//最后按照顺序输出即可
// for (auto i : cnt)
// cerr << i << " ";
return;
}
浙公网安备 33010602011771号