Codeforces Round 895 (Div. 3) 考试总结
前言
首先就是不太会打 CF,主要体现在晚上熬夜太难受,不过这场的状态还是挺好的。
然后就是时间把握不好,CCF 和模拟赛基本都是 \(3h\) 到 \(4h\),而 CF 只有 \(2h\) 左右。所以时间很紧,像这场,明明后面的题会做也没时间做了。
赛时实况:
A | B | C | D | E | F | G |
---|---|---|---|---|---|---|
√ | √ | √ | √ | × | × | × |
赛后改题情况:
A | B | C | D | E | F | G |
---|---|---|---|---|---|---|
√ | √ | √ | √ | √ | √ | √ |
只看了 F 题的 tj,其他的都是自己改的。
A. Two Vessels
Problem
有分别装有 \(a,b\) 单位水的两个杯子,容量无线大。现在有一个勺子,容量为 \(c\),每次可以从一个杯子里舀一勺不超过 \(c\) 单位的水(单位水可以不是整数),放入另一个杯子中。请问最少需要多少次操作才能使两个杯子里的水量相同。
Solve
水题。
总水量保持不变,所以两个杯子在操作完之后的单位水量为 \(\frac{a+b}{2}\),并且 \(|a-\frac{a+b}{2}|=|b-\frac{a+b}{2}|\)。所以最后的答案为 \(\left\lceil\dfrac{|a-\frac{a+b}{2}|}{c}\right\rceil\),向上取整是因为单次舀 \(c\) 单位水最后可能填不满。
Code
#include <bits/stdc++.h>
#define ll long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
using namespace std;
namespace Read {
template <typename T>
inline void read(T &x) {
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x*=f;
}
template <typename T, typename... Args>
inline void read(T &t, Args&... args) {
read(t), read(args...);
}
}
using namespace Read;
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
int T, a, b, c;
void solve() {
read(a, b, c);
cout << ceil(abs(1.0 * (a + b) / 2 - a) / c) << '\n';
}
signed main() {
read(T);
while(T--) {
solve();
}
return 0;
}
B. The Corridor or There and Back Again
Problem
有无限个房间,并且向右延伸。你现在需要从 \(1\) 号房间走到 \(k\) 号房间,并从 \(k\) 号房间回来。每向右走 \(1\) 单位需要耗费 \(1\) 单位的时间。这些房间里有些会有陷阱。共 \(n\) 个陷阱,每一个陷阱都有一个所在位置 \(d_i\) 和激活时间 \(s_i\),当你进入有陷阱的房间时,会激活陷阱,陷阱将在 \(s_i\) 秒后触发。陷阱触发后,你不能再次进入此房间或者从此房间出去。
求最远能到达的 \(k\) 号房间。
Solve
此题花费了我一点点的时间。
首先必须有去有回。当一个陷阱被出发后,你必须在某个点折返回来,并且要保证不能受陷阱影响。对于所有的陷阱,都是如此。每个陷阱都会有一个最大的,能到达并且能返回的点,将其记为 \(g_i\),答案为 \(\min\limits_{1\le i\le n} g_i\)。
考虑如何计算 \(g_i\),我们将去的路和回的路拉成一条直线,这条直线的中点即为 \(g_i=\frac{(d_i+(s_i-1))}{2}\)。
Code
#include <bits/stdc++.h>
#define ll long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
#define inf 1e9
using namespace std;
namespace Read {
template <typename T>
inline void read(T &x) {
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x*=f;
}
template <typename T, typename... Args>
inline void read(T &t, Args&... args) {
read(t), read(args...);
}
}
using namespace Read;
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
int T, n, d, s, k;
void solve() {
read(n);
k = inf;
For(i,1,n) {
read(d, s);
k = min(k, d + (s - 1) / 2);
}
cout << k << '\n';
}
signed main() {
read(T);
while(T--) {
solve();
}
return 0;
}
C. Non-coprime Split
Problem
给定 \(l,r\),求是否存在满足条件的 \(a,b\),使:
- \(l \le a+b\le r\)
- \(\gcd(a,b)\not= 1\)
若有多组解,任意输出一组。
Solve
构造题。
要意识到一点,就是 \(\gcd(a,a)=a\)(笑),然后对着 \(a+b\le r,a=b\) 构造就行了:
- 若 \(r\) 为偶数, \(a=b=\frac{r}{2}\);
- 若 \(r\) 为奇数,\(l<r\), \(a=b=\frac{r-1}{2}\);
若 \(r\) 为奇数,\(l=r\)。这时候就有点麻烦了。相当于已知 \(a+b=r,\gcd(a,b)\not=1\),求 \(a,b\)。
因为 \(\gcd(a,b)\not=1\),所以 \(\gcd(a,r-a)\not=1\)。根据欧几里得算法可知,\(\gcd(a,r-a)=\gcd(a,r)\not=1\),所以我们只要知道 \(a,r\) 是否有公共质因子即可。
\(a\) 是未知量,换个角度想,若 \(a\) 可以,则 \(a\) 的所有质因子也可以。由于是公共质因子,所以 \(\gcd(a,r)\) 的结果其实就是 \(r\) 的所有质因子。设 \(g\) 为 \(r\) 的最小质因子,则 \(a=g,b=r-g\)。
时间复杂度 \(O(T\sqrt n)\).
Code
#include <bits/stdc++.h>
#define ll long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
#define inf 1e9
using namespace std;
namespace Read {
template <typename T>
inline void read(T &x) {
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x*=f;
}
template <typename T, typename... Args>
inline void read(T &t, Args&... args) {
read(t), read(args...);
}
}
using namespace Read;
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
const int N = 1e7 + 10;
int T, l, r, p[N], len, prime[N], tot, st[N];
vector<int> v[N];
void init() {
for (int i = 2; i <= 1e7; i++) {
if(!st[i]) prime[++tot] = i;
for (int j = 1; j <= tot; j++) {
if(i * prime[j] > 1e7) break;
st[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
}
void solve() {
read(l, r);
if(r & 1) r--;
if(r < l) {
r++;
len = 0;
if(!st[r]) {
puts("-1");
return ;
}
For(i,2,sqrt(r)) {
if(st[i]) continue;
if(r % i == 0) {
cout << i << ' ' << r - i << '\n';
return ;
}
}
return ;
}
if(r / 2 == 1) {
puts("-1"); return ;
}
cout << r / 2 << ' ' << r / 2 << '\n';
}
signed main() {
read(T);
init();
while(T--) {
solve();
}
return 0;
}
D. Plus Minus Permutation
Problem
给定三个整数 \(n,x,y\),有排列 \(p_1,p_2,\dots,p_n\),规定这种排列的分数为:
求所有长度为 \(n\) 的排列的最大分数。
Solve
不是一眼能懂得题了。
分析一下:要想分数最大,\((p_{1 \cdot x} + p_{2 \cdot x} + \ldots + p_{\lfloor \frac{n}{x} \rfloor \cdot x})\) 就要最大,\((p_{1 \cdot y} + p_{2 \cdot y} + \ldots + p_{\lfloor \frac{n}{y} \rfloor \cdot y})\) 就要最小。排列的顺序可以自定。那么就贪心的将 \(n\sim n-\lfloor \frac{n}{x} \rfloor \cdot x+1\) 分配给 \(p_{1 \cdot x}, p_{2 \cdot x}, \ldots, p_{\lfloor \frac{n}{x} \rfloor \cdot x}\),将 \(1\sim \lfloor \frac{n}{y} \rfloor \cdot y\),贪心的分配给 \(p_{1 \cdot y}, p_{2 \cdot y}, \ldots, p_{\lfloor \frac{n}{y} \rfloor \cdot y}\)。
细节问题就是:那些 \(\frac{x·y}{\gcd(x,y)}\) 的位置贡献相当于 \(0\)(加减抵消)。所以这些位置要腾出来的话,可以多分配一些给有贡献的位置。
最后答案为:
看着有点吓人,其实写起来也有亿点点吓人
Code
#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
#define inf 1e9
using namespace std;
namespace Read {
template <typename T>
inline void read(T &x) {
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x*=f;
}
template <typename T, typename... Args>
inline void read(T &t, Args&... args) {
read(t), read(args...);
}
}
using namespace Read;
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
const int N = 1e7 + 10;
int T, n, x, y;
void solve() {
read(n, x, y);
int l1 = n / x, l2 = n / y, l3 = n / (x * y / __gcd(x, y));
l1 -= l3, l2 -= l3;
cout << ((2 * n - l1 + 1) * l1 / 2) - ((1 + l2) * l2 / 2) << '\n';
}
signed main() {
read(T);
while(T--) {
solve();
}
return 0;
}
E. Data Structures Fan
Problem
给定一个长度为 \(n\) 的数组和一个长度为 \(n\) 的二进制串 \(s\),现有两个操作:
1 l r
,表示将 \(l \le i \le r\) 的所有 \(s_i\) 取反(\(0\) 变 \(1\),\(1\) 变 \(0\));2 g
\((g\in {0,1})\),表示将所有 \(s_i=g\) 的 \(a_i\) 求异或和;
\(1\le n\le 10^5,1\le t\le 10^4\).
Solve
脑瘫题,不知道为啥这道题要放在 E。
首先,我们可以计算出操作二中一个串为 \(g=0\) 和 \(g=1\) 的初始异或和。分别记为 \(g_0,g_1\)。
对于操作一,无非是将 \(a_i\) 从 \(g_0\)( 或 \(g_1\))里删除,将其加入 \(g_1\)(或 \(g_0\))中,这个操作其实就是将 \(g_0,g_1\) 同时异或 \(l\sim r\) 的异或和。原理是异或的逆运算还是异或。
维护一个前缀异或和即可。但是我打的是线段树(因为一时脑抽没有想到前缀和
时间复杂度 \(O(Tq\log n)\),前缀和可以做到 \(O(Tq)\)。
Code
#include <bits/stdc++.h>
#define ll long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
#define ls p<<1
#define rs p<<1|1
using namespace std;
namespace Read {
template <typename T>
inline void read(T &x) {
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x*=f;
}
template <typename T, typename... Args>
inline void read(T &t, Args&... args) {
read(t), read(args...);
}
}
using namespace Read;
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
const int N = 1e5 + 10;
struct Node {
int l, r, val;
} t[N << 2];
int n, T, q, a[N], p0, p1;
string s;
void pushup(int p) {
t[p].val = t[ls].val ^ t[rs].val;
}
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if(l == r) {
t[p].val = a[l];
return ;
}
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
pushup(p);
}
int ask(int p, int l, int r) {
if(l <= t[p].l && t[p].r <= r) {
return t[p].val;
}
int mid = (t[p].l + t[p].r) >> 1, ans = 0;
if(l <= mid) ans ^= ask(ls, l, r);
if(r > mid) ans ^= ask(rs, l, r);
return ans;
}
void solve() {
p0 = p1 = 0;
read(n);
For(i,1,n) read(a[i]);
cin >> s;
s = " " + s;
For(i,1,n) {
if(s[i] == '0') p0 ^= a[i];
else p1 ^= a[i];
}
build(1, 1, n);
read(q);
while(q--) {
int op; read(op);
if(op == 1) {
int l, r, p;
read(l, r);
p = ask(1, l, r);
p0 ^= p, p1 ^= p;
} else {
int g; read(g);
if(g == 0) cout << p0 << ' ';
else cout << p1 << ' ';
}
}
cout << '\n';
}
signed main() {
read(T);
while(T--) {
solve();
}
return 0;
}
F. Selling a Menagerie
Problem
动物园里有 \(n\) 个动物,第 \(i\) 个动物害怕第 \(a_i\) 个动物,第 \(i\) 个动物价值 \(c_i\) 元。现在我要将这些动物全部卖掉。显然,卖掉的动物编号可以构成一个排列 \(p\)。
考虑卖掉这些动物时:
- 若 \(a_i\) 在 \(i\) 还没有卖掉之前就被卖掉了,现在卖掉 \(i\),可以获得 \(c_i\) 元;
- 若 \(a_i\) 在 \(i\) 还没有卖掉之前没被卖掉,现在卖掉 \(i\),可以获得 \(2·c_i\) 元;
求最多能赚多少钱。
Solve
首先,对于动物 \(i\) 来说,肯定希望它害怕动物 \(a_i\) 没有被卖掉。这样可以卖一个好价钱。
反过来想:如果一个动物没有任何其他的动物怕它,那么现在卖掉这个动物之后所得到的贡献肯定不劣。因为它的存在不会影响到其他动物卖出所得的贡献。
如果把“害怕”看成一种连边关系,那么问题就很好解决了。
连边之后,会出现没有构成环的点,和构成环的点。没有构成环的点的卖出顺序随意,上文已经解释过了(没有构成环的点的存在不会影响到其他动物卖出所得的贡献)。不过要注意的是,每一条链上的点或导出子树上的点要按顺序选,不过其都会被选中。
而构成环的点的卖出顺序至关重要,换句话说,起点和终点的所在位置非常关键。起点肯定是要选择 \(c_i\) 最小的点,让损失尽可能的小。然后按顺序选择,卖出即可。
由于每一个点只会被遍历一次,时间复杂度 \(O(Tn)\)。
Code
#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
#define inf 1e10
using namespace std;
namespace Read {
template <typename T>
inline void read(T &x) {
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x*=f;
}
template <typename T, typename... Args>
inline void read(T &t, Args&... args) {
read(t), read(args...);
}
}
using namespace Read;
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
const int N = 1e5 + 10;
int T, n, a[N], d[N], c[N], ans, Ans[N], len;
bool vis[N];
void solve() {
memset(vis, 0, sizeof vis);
ans = len = 0;
read(n);
For(i,1,n) read(a[i]), d[a[i]]++;
For(i,1,n) read(c[i]);
bool f = 1;
while(f) {
f = 0;
For(i,1,n) {
if(!d[i] && !vis[i]) Ans[++len] = i, d[a[i]]--, vis[i] = 1, f = 1;
}
}
For(i,1,n) {
if(d[i]) {
int x = i, mn = inf, en, st;
while(!vis[x]) {
vis[x] = 1;
if(mn > c[x]) {
mn = c[x];
st = a[x];
en = x;
}
d[a[x]]--;
x = a[x];
}
while(st != en) {
Ans[++len] = st;
st = a[st];
}
Ans[++len] = en;
}
}
For(i,1,len) cout << Ans[i] << ' ';
cout << '\n';
}
signed main() {
read(T);
For(i,1,T) {
solve();
}
return 0;
}
G. Replace With Product
Problem
给定一个长度为 \(n\) 的数组 \(a\),现在可以进行一次操作:
- 选择两个正整数 \(l,r(l\le r)\),将 \(a[l\dots r]\) 的所有数删除,并替换为 \(\prod\limits_{i=l}^ra_i\);
最后的总贡献为所有数的加和,请选择 \(l,r\),最大化总贡献。
Solve
小 trick 题。
首先,前缀和后缀 \(1\) 选入操作区间显然是不会优的,因为他对于乘法是没有贡献的,而对于加法有 \(+1\) 的贡献。
发现一个性质:如果一些数的乘积很大,达到了某一个上限时,这些数的和一定比这些数的乘积要小。这个上限具体为多少我们并不知道,但是,只要它是一个很大的数字,那么上述事件发生的概率就会很大。考虑到 \(1 \le a_i \le 10^9\),我们就将上限设为 \(10^{18}\),这是一个接近于 \(2^{60}\) 的数字。这也就意味着,当所有数的乘积小于 \(10^{18}\) 时,所有大于 \(2\) 的数字不会超过 \(60\) 个。
对于所有乘积大于上限的数,直接摒弃前缀和后缀 \(1\) 之后的区间即为答案,因为要尽可能的让乘积变大。
显然,操作区间的左右端点不会出现在数字 \(1\) 上,因为出现在了数字 \(1\) 上肯定会比出现在大于 \(1\) 的数字上更劣。所以只要暴力枚举所有大于 \(1\) 的数的位置,然后利用前缀和与前缀积快速统计贡献,最后取最大贡献所对应的区间即可。
时间复杂度 \(O(TnK^2)\),其中 \(K\le 60\)。
Code
#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
#define inf 1e18
using namespace std;
namespace Read {
template <typename T>
inline void read(T &x) {
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x*=f;
}
template <typename T, typename... Args>
inline void read(T &t, Args&... args) {
read(t), read(args...);
}
}
using namespace Read;
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
const int N = 2e5 + 10;
int T, n, a[N], sum[N], ans, ansl, ansr, len, no1[N], pre[N];
void solve() {
read(n);
For(i,1,n) read(a[i]), sum[i] = sum[i-1] + a[i];
int l, r, k; __int128 p = 1; len = 0, ans = 0, ansl = 1, ansr = 1;
For(i,1,n) {
p *= a[i];
if(p > inf) break;
}
if(p > inf) {
For(i,1,n) {
l = i;
if(a[i] != 1) {
break;
}
}
FOR(i,n,1) {
r = i;
if(a[i] != 1) {
break;
}
}
cout << l << ' ' << r << '\n';
return ;
}
pre[0] = 1;
For(i,1,n) pre[i] = pre[i-1] * a[i];
For(i,1,n) {
if(a[i] != 1) no1[++len] = i;
}
For(i,1,len) {
For(j,1,len) {
if(no1[i] > no1[j]) continue;
if(ans < sum[n] - (sum[no1[j]] - sum[no1[i]-1]) + (pre[no1[j]] / pre[no1[i]-1])) {
ans = sum[n] - (sum[no1[j]] - sum[no1[i]-1]) + (pre[no1[j]] / pre[no1[i]-1]);
ansl = no1[i], ansr = no1[j];
}
}
}
cout << ansl << ' ' << ansr << '\n';
}
signed main() {
read(T);
while(T--) {
solve();
}
return 0;
}