A.Subsegment Reverse
题意
给出三个正整数 N , L , R N, L, R N,L,R。
对于一个序列 A = ( 1 , 2 , … , N ) A = (1, 2, \ldots, N) A=(1,2,…,N),请你输出翻转了 L ∼ R L \sim R L∼R之间数字后得到的序列。
分析
使用循环进行翻转即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5e2;
int a[N];
void solve() {
int n, l, r;
cin >> n >> l >> r;
for (int i = 1; i <= n; i++) {
a[i] = i;
}
for (int i = l, j = r; i <= j; i++, j--) {
swap(a[i], a[j]);
}
for (int i = 1; i <= n; i++) {
cout << a[i] << ' ';
}
}
int main () {
solve();
return 0;
}
B.Nutrients
题意
高桥每天需要补充 M M M种营养物质,第 j j j种营养物质至少需要补充 A j A_j Aj单位。
今天,他将会吃掉 N N N种食物,其中第 i i i种食物能依次获得 X i , 1 , X i , 2 , … , X i , M X_{i, 1}, X_{i, 2}, \ldots, X_{i, M} Xi,1,Xi,2,…,Xi,M营养,其中 X i , j X_{i, j} Xi,j表示第 i i i种食物提供的 j j j营养物质的数量。
问:今天能否获得足够的营养?
分析
直接在输入的 A A A数组中减去每个食物对应的营养即可。
如果输入完成后, A A A数组里只存在负数和 0 0 0,那么就表示获得了足够的营养,否则。说明没有获得足够的营养。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5e2;
int a[N];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int x;
cin >> x;
a[j] -= x;
}
}
for (int i = 1; i <= m; i++) {
if (a[i] > 0) {
cout << "No" << endl;
return;
}
}
cout << "Yes" << endl;
}
int main () {
solve();
return 0;
}
C.Keys(二进制枚举)
题意
有 N N N把钥匙,其中有真有假。
有一扇门,每次可以选择若干把钥匙一起开门,如果其中至少包含 k k k把真的钥匙,那么就可以把门打开。
你对这个门尝试了 M M M次以下操作:
-
选择 C i C_i Ci把钥匙 A i , 1 , A i , 2 , … , A i , C i A_{i, 1}, A_{i, 2}, \ldots, A_{i, C_i} Ai,1,Ai,2,…,Ai,Ci,并尝试打开门。
-
获得当前尝试的结果 R i R_i Ri:
-
如果 R i = ′ o ′ R_i = 'o' Ri=′o′:表示门被打开了
-
如果 R i = ′ x ′ R_i = 'x' Ri=′x′:表示门未被打开
-
已知钥匙的真假情况共有 2 N 2^{N} 2N种,请问其中有多少种情况满足 M M M次操作的结果。
分析
由于 n ≤ 15 n \le 15 n≤15,那么 2 n ≤ 2 15 = 32768 2^{n} \le 2^{15} = 32768 2n≤215=32768,因此,总的方案数是很小的,可以使用一个长度为 n n n的二进制串表示钥匙的真假情况,即二进制串第 i i i位为 0 0 0表示第 i i i把钥匙为假,第 i i i位为 1 1 1表示第 i i i把钥匙为真。
然后枚举所有可能的二进制串 ( 0 ∼ 2 n − 1 ) (0 \sim 2^{n} - 1) (0∼2n−1),并检查每个二进制串是否满足题目要求,即能打开门的方案中真钥匙的数量是否大于等于 k k k,不能打开门的方案中真钥匙的数量是否小于 k k k。如果满足题目要求,就记录答案数量加一。
最后输出记录的答案数量即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3e2 + 5e2;
int n, m, k, c[N], r[N], a[N][N];
bool check(int x) {
for (int i = 1; i <= m; i++) {
int cnt = 0;
for (int j = 1; j <= c[i]; j++) {
if ((x & (1 << (a[i][j] - 1))) != 0) {
cnt++;
}
}
if (cnt >= k && r[i] == 1 || cnt < k && r[i] == 0) {} else {
return 0;
}
}
return 1;
}
void solve() {
cin >> n >> m >> k;
for (int i = 1; i <= m; i++) {
cin >> c[i];
for (int j = 1; j <= c[i]; j++) {
cin >> a[i][j];
}
char op;
cin >> op;
if (op == 'o') r[i] = 1;
else r[i] = 0;
}
int ans = 0;
for (int i = (1 << n) - 1; i >= 0; i--) {
if (check(i)) {
ans++;
}
}
cout << ans << endl;
}
int main () {
solve();
return 0;
}
D.Masked Popcount(数学)
题意
给出两个数字 N , M N, M N,M,请你计算KaTeX parse error: Expected 'EOF', got '&' at position 40: …opcount(k\text{&̲}M)取模 998244353 998244353 998244353的结果。
分析
先不考虑 M M M,先考虑 ∑ k = 0 N p o p c o u n t ( k ) \sum\limits_{k = 0}^{N}popcount(k) k=0∑Npopcount(k)的结果。
使用 a n s [ i ] ans[i] ans[i]表示 1 ∼ N 1 \sim N 1∼N之间二进制第 i i i为为一的数字个数。
那么 ∑ k = 0 N p o p c o u n t ( k ) = ∑ i = 0 59 a n s [ i ] \sum\limits_{k = 0}^{N}popcount(k) = \sum\limits_{i = 0}^{59}ans[i] k=0∑Npopcount(k)=i=0∑59ans[i]。
然后打表来找一下规律:
- n = 1 n = 1 n=1时, a n s = [ 1 ] ans = [1] ans=[1]
- n = 2 n = 2 n=2时, a n s = [ 1 , 1 ] ans = [1, 1] ans=[1,1]
- n = 4 n = 4 n=4时, a n s = [ 2 , 2 , 1 ] ans = [2, 2, 1] ans=[2,2,1]
- n = 8 n = 8 n=8时, a n s = [ 4 , 4 , 4 , 1 ] ans = [4, 4, 4, 1] ans=[4,4,4,1]
- n = 16 n = 16 n=16时, a n s = [ 8 , 8 , 8 , 8 , 1 ] ans = [8, 8, 8, 8, 1] ans=[8,8,8,8,1]
- …
- n = 2 i n = 2^{i} n=2i时, a n s = [ 2 i − 1 , 2 i − 1 , … , 2 i − 1 , 1 ] ans = [2^{i - 1}, 2^{i - 1}, \ldots, 2^{i - 1}, 1] ans=[2i−1,2i−1,…,2i−1,1]
这样就可以找到 n n n为 2 2 2的次方数,对每位二进制产生的贡献。
那如果 n = 2 i + a ( a < 2 i ) n = 2^{i} + a(a < 2^{i}) n=2i+a(a<2i)呢?
不难想到, 2 i ∼ 2 i + a 2^{i} \sim 2^{i} + a 2i∼2i+a最高的二进制位固定为 1 1 1,那么共有 1 + a 1 + a 1+a个这样的数字,对第 i i i个二进制位产生的贡献即为 1 + a 1 + a 1+a,然后让 n n n减去 2 i 2^{i} 2i,继续计算剩余的 n n n还能产生的贡献即可。
因此,可以依次枚举 59 ∼ 0 59 \sim 0 59∼0,如果 n ≥ 2 i n \ge 2^{i} n≥2i,那么就让所有的 a n s [ j ] ( j = 0 ∼ ( i − 1 ) ) ans[j](j = 0 \sim (i - 1)) ans[j](j=0∼(i−1))均加上 2 i − 1 2^{i - 1} 2i−1,并让 a n s [ i ] ans[i] ans[i]加上 1 + n 1 + n 1+n % 2 i 2^{i} 2i,然后让 n n n减去 2 i 2^{i} 2i。
这样,就可以得到了 ∑ k = 0 N p o p c o u n t ( k ) = ∑ i = 0 59 a n s [ i ] \sum\limits_{k = 0}^{N}popcount(k) = \sum\limits_{i = 0}^{59}ans[i] k=0∑Npopcount(k)=i=0∑59ans[i],然后计算 p o p c o u n t popcount popcount时需要让KaTeX parse error: Expected 'EOF', got '&' at position 9: k \text{&̲} m,那么不难想到,答案即为所有 m m m的二进制位上为 1 1 1对应的 a n s [ i ] ans[i] ans[i]之和。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
ll ans[105];
void solve() {
ll n, m;
cin >> n >> m;
for (int i = 60; i >= 0; i--) {
if (n >= (1ll << i)) {
ans[i] = ((ans[i] + 1) % mod + n % (1ll << i)) % mod;
for (int j = i - 1; j >= 0; j--) {
ans[j] = (ans[j] + (1ll << (i - 1)) % mod) % mod;
}
n -=(1ll << i);
}
}
ll res = 0;
for (int i = 60; i >= 0; i--) {
if (m & (1ll << i))
res = (res + ans[i]) % mod;
}
cout << res << endl;
}
int main () {
solve();
return 0;
}
E.Max/Min(前缀和)
题意
给出一个序列 A = ( A 1 , … , A n ) A = (A_1, \ldots, A_n) A=(A1,…,An),请你求出:
- ∑ i = 1 n − 1 ∑ j = i + 1 n ⌊ m a x ( A i , A j ) m i n ( A i , A j ) ⌋ \sum\limits_{i = 1}^{n - 1}\sum\limits_{j = i + 1}^{n}\lfloor \frac{max(A_i, A_j)}{min(A_i, A_j)} \rfloor i=1∑n−1j=i+1∑n⌊min(Ai,Aj)max(Ai,Aj)⌋
分析
不难想到,如果 A i < A j A_i < A_j Ai<Aj,那么 ⌊ m a x ( A i , A j ) m i n ( A i , A j ) ⌋ = ⌊ A j A i ⌋ \lfloor \frac{max(A_i, A_j)}{min(A_i, A_j)} \rfloor = \lfloor \frac{A_j}{A_i} \rfloor ⌊min(Ai,Aj)max(Ai,Aj)⌋=⌊AiAj⌋。
因此,可以考虑对所有的 A i A_i Ai,统计 ∑ j = i + 1 n ⌊ A j A i ⌋ \sum\limits_{j = i + 1}^{n}\lfloor \frac{A_j}{A_i} \rfloor j=i+1∑n⌊AiAj⌋。
然后考虑怎么快速计算出 ∑ j = i + 1 n ⌊ A j A i ⌋ \sum\limits_{j = i + 1}^{n}\lfloor \frac{A_j}{A_i} \rfloor j=i+1∑n⌊AiAj⌋,由于选择的是向下取整,那么对于数字 x x x而言, y = x × j ∼ ( x × ( j + 1 ) ) − 1 y = x \times j \sim (x \times (j + 1)) - 1 y=x×j∼(x×(j+1))−1得到的 ⌊ y x ⌋ \lfloor \frac{y}{x} \rfloor ⌊xy⌋均是相同的,因此,可以预处理前缀和,以便于快速知道 x × j ∼ ( x × ( j + 1 ) ) − 1 x \times j \sim (x \times (j + 1)) - 1 x×j∼(x×(j+1))−1之间的数字个数,此时产生的贡献即为 j × ( p r e [ ( x × ( j + 1 ) ) − 1 ] − p r e [ x × j − 1 ] ) × c n t j \times (pre[(x \times (j + 1)) - 1] - pre[x \times j - 1]) \times cnt j×(pre[(x×(j+1))−1]−pre[x×j−1])×cnt,其中 c n t cnt cnt为数字 x x x的出现次数。
因此,可以使用 s u m sum sum数组记录每个数字的出现次数,然后对记录的数字出现次数预处理前缀和。
然后,就可以枚举数字 i i i了,对于数字 i i i,再枚举 j j j从 1 1 1到 i × j ≤ 1 0 6 i \times j \le 10^{6} i×j≤106的范围内。统计所有数字产生的贡献即可。
此时计算过程中会多计算了一部分,需要在计算后减去。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5e2;
ll sum[N], pre[N];
int main() {
int n;
cin >> n;
for (int i = 1, num; i <= n; i++) {
cin >> num;
sum[num]++;
}
for (int i = 1; i <= 1e6; i++) {
pre[i] = pre[i - 1] + sum[i];
}
ll ans = 0;
for (int i = 1; i <= 1e6; i++) {
for (int j = 1; j * i <= 1e6; j++) {
ans += j * (pre[min((j + 1) * i - 1, 1000000)] - pre[j * i - 1]) * sum[i];
}
ans -= sum[i] * (1 + sum[i]) / 2;
}
cout << ans << endl;
return 0;
}
F.Distance Component Size Query
更新中…
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。