A.Setting up Camp(思维)
题意:
组委会计划在游览结束后带领奥林匹克运动会的参赛选手进行徒步旅行。目前,正在计算需要的帐篷数量。据了解,每个帐篷最多可容纳333人。
在参赛者中,有aaa个内向者,bbb个外向者,ccc个普通人:
- 每个内向者都想独自住在帐篷里。因此,内向者的帐篷里必须只有一个人–只有内向者自己。
- 每个外向的人都想和另外两个人住在一个帐篷里。因此,一个外向者的帐篷里必须正好有三个人。
- 每个人都可以选择任何一种方式(独居、与他人同住或与他人同住)。
组委会非常尊重每位参赛者的意愿,因此他们希望满足所有参赛者的愿望。
请告诉我们至少需要多少顶帐篷,以便所有参加者都能根据自己的喜好找到住处。如果无法满足所有参与者的愿望,输出−1-1−1。
分析:
内向者一人一个帐篷,普通人哪里不够塞哪里。外向者必须三人一个帐篷,优先给外向者三人分一个帐篷,若剩下不足三人,用普通人补上,如果补不上,就无解,否则有解。
代码:
#include<bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int a, b, c;
cin >> a >> b >> c;
int d = (3 - b % 3) % 3;
int ans = a + (b + c + 2) / 3;
if (d > c)
cout << -1 << endl;
else
cout << ans << endl;
}
return 0;
}
B.Fireworks(贪心)
题意:
徒步旅行的其中一天恰逢节假日,因此决定晚上在营地安排一场节日焰火表演。为此,徒步旅行的组织者购买了两个烟花发射装置和大量的烟花弹。
两个装置同时开启。第一个装置每隔aaa分钟(即发射后a,2⋅a,3⋅a,…a,2\cdot a,3\cdot a,\dotsa,2⋅a,3⋅a,…分钟)发射一次烟花。第二个装置每隔bbb分钟(即发射后b,2⋅b,3⋅b,…b,2\cdot b,3\cdot b,\dotsb,2⋅b,3⋅b,…分钟)发射一次烟花。
每个烟花在发射后的m+1m+1m+1分钟内都可以在天空中看到,也就是说,如果一个烟花是在装置开启后的xxx分钟后发射的,那么从xxx到x+mx+mx+m(包括首尾两分钟),每分钟都可以看到一个烟花。如果一个烟花在另一个烟花mmm分钟后发射,则两个烟花的可见时间均为一分钟。
天空中最多可以同时看到多少枚烟花?
分析:
题意可以转换为找一个时间段,长为m+1m+1m+1,使得这个时间段上释放的烟花最多。贪心的想,这个时间段左端点应该是两个烟花同时释放,因此aaa和bbb的最小公倍数即是两个烟花同时释放的时刻。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
LL a, b, m;
cin >> a >> b >> m;
cout << (m + a) / a + (m + b) / b << endl;
}
return 0;
}
C.Left and Right Houses(前缀和)
题意:
莱托沃村有nnn栋房子。村民们决定修建一条大路,将村庄分为左右两边。每个居民都想住在街道的右侧或左侧,这可以描述为一个序列a1,a2,…,ana_1,a_2,\dots,a_na1,a2,…,an,如果jjj这栋房子的居民想住在街道的左侧,则为aj=1a_j=1aj=1;否则为aj=0a_j=0aj=0。
道路将从两栋房子之间穿过。左边的房子将被宣布为左侧,右边的房子将被宣布为右侧。更具体地说,让道路从房屋iii和i+1i+1i+1之间通过。那么位于111和iii之间的房屋将位于街道的左侧,位于i+1i+1i+1和nnn之间的房屋将位于街道的右侧。在这种情况下,整个村庄将被分割为右侧或左侧。
为了使设计公平,我们决定在铺设道路时,至少要让村子两边各一半的居民对选择感到满意。也就是说,在一边的xxx居民中,至少有⌈x2⌉\lceil\frac{x}{2}\rceil⌈2x⌉希望住在那一边,其中⌈x⌉\lceil x\rceil⌈x⌉表示四舍五入的实数xxx。

在路的左边,会有iii幢房子,在相应的aja_jaj中,至少要有⌈i2⌉\lceil\frac{i}{2}\rceil⌈2i⌉个000。路的右边有n−in-in−i座房子,在相应的aja_jaj中至少要有⌈n−i2⌉\lceil\frac{n-i}{2}\rceil⌈2n−i⌉个111。
确定在哪座房子iii之后铺设道路,以满足所述条件,并尽可能靠近村庄中央。形式上,在所有合适的位置iii中,最小化∣n2−i∣\left|\frac{n}{2}-i\right|2n−i。
如果有多个合适的位置iii,最小值∣n2−i∣\left|\frac{n}{2}-i\right|2n−i,则输出较小的一个。
分析:
分割一个序列,使其左半部分的000要多于111,右半部分的111要多于000。
前缀和统计000和111的数量,然后枚举分割线的位置即可。
代码:
#include<bits/stdc++.h>
using namespace std;
void solve() {
int n;
cin >> n;
string s;
cin >> s;
s = " " + s;
vector<int> num0(n + 1), num1(n + 1);
for (int i = 1; i <= n; i++) {
if (s[i] == '0')
num0[i]++;
else
num1[i]++;
num0[i] += num0[i - 1];
num1[i] += num1[i - 1];
}
int pos = -1;
if (num1[n] >= (n + 1) / 2) pos = 0;
for (int i = 1; i <= n; i++) {
int suml = num0[i];
int sumr = num1[n] - num1[i];
if (suml >= (i + 1) / 2 && sumr >= (n - i + 1) / 2) {
if (pos == -1)
pos = i;
else {
double dis0 = abs(double(n) / 2 - i);
double dis1 = abs(double(n) / 2 - pos);
if (dis0 < dis1)
pos = i;
}
}
}
cout << pos << endl;
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
D.Seraphim the Owl(思维)
题意:
大家排成长度为nnn的一队,从i=1i=1i=1号开始,向猫头鹰谢拉菲姆请教生命的意义。不幸的是,基里尔当时正忙着为这个问题编写传说,所以他来得晚了一些,站在了这nnn个人之后,排在了队伍的最后。基里尔对这种情况完全不满意,于是他决定贿赂一些排在他前面的人。
对于队列中的第iii个人,基里尔知道两个值:aia_iai和bib_ibi。如果此刻基里尔站在位置iii上,那么他可以选择任意一个位置jjj,并与位置jjj的人交换位置。在这种情况下,基里尔需要支付他aja_jaj个金币。而对于j<k<ij\lt k\lt ij<k<i的每一个kkk,基里尔都要向位置kkk的人支付bkb_kbk个金币。基里尔可以多次执行这个操作。
基里尔很节俭,所以他想花尽可能少的硬币,但是他又不想等太久,所以基里尔认为他应该排在mmm人的前面。
请帮助基里尔确定,为了不等太久,他最少需要花费多少金币。
分析:
当i>mi>mi>m时 如果想跟第iii个人换位是不影响第i+1i+1i+1个人的决策的,也就是说可以先和第i+1i+1i+1个人换位再换到iii,也可以直接换到iii。以此类推大于mmm时直接取aia_iai和bib_ibi的最小值即可。
枚举停在前面所有位置上时的花费,不过发现只有停到的某个位置iii上必须支付aia_iai以外,后面 i+1∼ni+1∼ni+1∼n的位置的代价都是aj,bja_j,b_jaj,bj值取其小值。所以我们从后向前枚举iii,后面部分的代价是不会变的,直接累加起来即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 2e5 + 5;
const LL inf = 0x3f3f3f3f3f3f3f;
int n, m;
LL a[N], b[N];
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
cin >> b[i];
LL sum = 0;
LL mx = inf;
for (int i = m + 1; i <= n; i++)
sum += min(a[i], b[i]);
for (int i = n - 1; i; i--)
b[i] += b[i + 1];
b[n + 1] = 0;
for (int i = 1; i <= m; i++) {
mx = min(a[i] + b[i + 1] - b[m + 1] + sum, mx);
}
cout << mx << endl;
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
E.Binary Search(二分)
题意:
安东在徒步旅行中感到无聊,想解决一些问题。他问基里尔有没有新问题,基里尔当然有。
给你一个大小为nnn的排列数ppp和一个需要找到的数字xxx。长度为nnn的排列是由nnn个不同的整数组成的数组,这些整数从111到nnn按任意顺序排列。例如,[2,3,1,5,4][2,3,1,5,4][2,3,1,5,4]是一个排列,但[1,2,2][1,2,2][1,2,2]不是一个排列(222在数组中出现了两次),[1,3,4][1,3,4][1,3,4]也不是一个排列(n=3n=3n=3,但数组中有444)。
你认为自己是一个很酷的程序员,所以你将使用一种高级算法进行搜索–二分查找。但是,你忘了二分查找必须对数组进行排序。
为了得到正确的答案,你可以在运行算法前执行不超过222次的以下操作:选择索引iii,jjj(1≤i,j≤n1\le i,j\le n1≤i,j≤n),并交换位置iii和jjj的元素。
然后进行二分查找。在算法开始时,声明了两个变量l=1l=1l=1和r=n+1r=n+1r=n+1。然后执行以下循环:
- 如果r−l=1r-l=1r−l=1,结束循环
- m=⌊r+l2⌋m=\lfloor\frac{r+l}{2}\rfloorm=⌊2r+l⌋
- 若pm≤xp_m\le xpm≤x,赋值l=ml=ml=m,否则r=mr=mr=m。
我们的目标是在算法之前重新排列排列组合中的数字,以便在算法执行之后,plp_lpl等于xxx。可以证明,222次运算总是足够的。
分析:
手推一下,整个区间的大部分数都是没用的,可以发现整个过程中mmm一定不会等于111,因为只有当L=1L=1L=1,R=2R=2R=2时(L+R)/2(L+R)/2(L+R)/2才会等于111,但是R−L=1R-L=1R−L=1时会终止,所以把xxx换到位置111能保证xxx不出现在二分的过程中。然后模拟一遍二分的过程找出最终的LLL,交换111和LLL的位置即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 2e5 + 5;
int n, m;
int a[N], pos[N];
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
pos[a[i]] = i;
}
cout << 2 << endl;
int l = 1;
int r = n + 1;
int mid;
cout << 1 << ' ' << pos[m] << endl;
swap(a[1], a[pos[m]]);
while (r - l != 1) {
mid = l + r >> 1;
if (a[mid] <= m)
l = mid;
else
r = mid;
}
cout << l << ' ' << 1 << endl;
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
F.Kirill and Mushrooms(思维)
题意:
营地里的人一睡着,基里尔就偷偷溜出帐篷,去智慧橡树那里采蘑菇。
众所周知,橡树下生长着nnn种蘑菇,每种蘑菇都有神奇的力量viv_ivi。基里尔非常想用这些蘑菇制作一种魔力最大的灵药。
灵药的强度等于其中蘑菇的数量与这些蘑菇中最小魔力的乘积。要配制灵药,基里尔要依次采摘生长在橡树下的蘑菇。基里尔可以按照任何顺序采集蘑菇。
然而,事情并非如此简单。智慧橡树告诉基里尔从111到nnn的数字ppp的排列组合。如果基里尔只采摘kkk个蘑菇,那么所有指数为p1,p2,…,pk−1p_1,p_2,\dots,p_{k-1}p1,p2,…,pk−1的蘑菇的魔力就会变成000。基里尔不会使用魔力为零的蘑菇来配制灵药。
你的任务是帮助基里尔采集蘑菇,使他能够酿造出最大魔力的灵药。不过,基里尔有点害怕在橡树附近逗留太久,所以在所有合适的采集蘑菇的选项中,他要求你找到蘑菇数量最少的那个。
长度为nnn的排列是由nnn个不同的整数组成的数组,这些整数的顺序从111到nnn。例如,[2,3,1,5,4][2,3,1,5,4][2,3,1,5,4]是一个排列,但[1,2,2][1,2,2][1,2,2]不是排列(222在数组中出现了两次),[1,3,4][1,3,4][1,3,4]也不是排列(n=3n=3n=3,但444在数组中出现)。
分析:
如果枚举出kkk就可以确定那些蘑菇的力量值为000,然后在剩下的蘑菇中贪心的选择最大的kkk个,这样就可以知道选择的蘑菇中的最小魔力值。
用multiset<int>维护两个集合,一个是cntcntcnt表示当前不为000且没有被选到的蘑菇,qqq是当前被选到的最大的kkk个蘑菇。
然后从小到大枚举kkk,每次都从cntcntcnt中取最大值填入到qqq中,直到q.size()==kq.size()==kq.size()==k。然后随着选择数量增大,一些蘑菇魔力变为000,从两个集合中删掉一些蘑菇,每次删除时优先从cntcntcnt中删除即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL inf = 1e18;
void solve() {
LL n;
cin >> n;
multiset<LL> cnt, q;
vector<LL> a(n + 5);
vector<LL> p(n + 5);
for (LL i = 1; i <= n; i++) {
cin >> a[i];
q.insert(a[i]);
}
for (LL i = 1; i <= n; i++)
cin >> p[i];
LL res = -inf;
LL val = -inf;
for (LL i = 1; i <= n; i++) {
while (not q.empty() && cnt.size() < i) {
LL x = *q.rbegin();
cnt.insert(x);
q.erase(q.find(x));
}
if (cnt.size() < i)
break;
LL v = *cnt.begin() * i;
if (v > res) {
res = v;
val = i;
}
if (q.count(a[p[i]]))
q.erase(q.find(a[p[i]]));
else
cnt.erase(cnt.find(a[p[i]]));
}
cout << res << " " << val << endl;
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

1418

被折叠的 条评论
为什么被折叠?



