A.Soccer(思维)
题意:
迪马喜欢看足球比赛。在这样一场比赛中,记分牌上的比分表示为 x x x: y y y,其中 x x x是第一队的进球数, y y y是第二队的进球数。在任何时候,只有一支球队可以进球,因此比分 x x x: y y y可以变为 ( x + 1 ) (x+1) (x+1): y y y,或者 x x x: ( y + 1 ) (y+1) (y+1)。
在观看足球比赛时,迪马被一些非常重要的事情分散了注意力,过了一段时间,他又重新开始观看比赛。迪马记得他走神前的比分和他回来后的比分。鉴于这两个比分,他想知道以下问题。有没有可能在迪马没有看比赛的时候,两队的比分从来没有相同过?
可以肯定的是,在迪马记得的两个时间点上,两队的比分都不相等。不过,也有可能在他不在的时候,比分并没有发生变化。
请帮助迪马回答问题!
分析:
我们将之前的比分表示为 [ x 1 , y 1 ] [x_1,y_1] [x1,y1],之后的比分表示为 [ x 2 , y 2 ] [x_2,y_2] [x2,y2]。二者不相交则不可能出现相同的分数。
代码:
#include<bits/stdc++.h>
using namespace std;
void solve() {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
if (x1 < y1) {
if (x2 >= y2) {
cout << "NO" << endl;
} else {
cout << "YES" << endl;
}
} else if (x1 > y1) {
if (x2 <= y2) {
cout << "NO" << endl;
} else {
cout << "YES" << endl;
}
} else {
cout << "YES" << endl;
}
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
B.Collatz Conjecture(思维、数学)
题意:
最近,一年级学生马克西姆学习了科拉兹猜想,但他在讲课时没有太注意,所以他认为猜想中提到了以下过程:
有一个变量 x x x和一个常数 y y y。下面的操作进行了 k k k次:
- 将 x x x增加 1 1 1,然后
- 当数字 x x x能被 y y y整除时,再除以 y y y。
请注意,这两个操作都是在一次操作中依次进行的。
例如,如果数字 x = 16 x=16 x=16、 y = 3 y=3 y=3和 k = 2 k=2 k=2,那么经过一次运算后, x x x就变成了 17 17 17,而经过另一次运算后, x x x就变成了 2 2 2,因为加一后, x = 18 x=18 x=18就能被 3 3 3整除两次。
鉴于初始值为 x x x、 y y y和 k k k,马克西姆想知道 x x x的最终值是多少。
分析:
直接模拟会超时。考虑如果 x x x等于 1 1 1,可以直接跳出循环,用数学方法解决,因为这时 x x x就一直会在 [ 1 , y ] [1,y] [1,y]之间。
代码:
#include<bits/stdc++.h>
using namespace std;
void solve() {
int x, y, k;
cin >> x >> y >> k;
while (k != 0 && x != 1) {
int t = min(k, y - x % y);
x += t;
k -= t;
while (x % y == 0) {
x /= y;
}
}
k %= (y - 1);
x = x + k;
cout << x << endl;
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
C.Boring Day(贪心)
题意:
又是无聊的一天,埃戈尔觉得无聊,决定做点什么。但因为没有朋友,他就想出了一个游戏。
埃戈尔有一副 n n n张扑克牌,最上面的 i i i张牌上写着数字 a i a_i ai。埃戈想玩一定轮数的游戏,直到牌用完为止。在每一轮中,他都会从牌面顶端抽取一张非零数的牌,然后结束这一轮。如果在这一轮中收集到的纸牌上的数字之和在 l l l和 r r r(包括首尾数字)之间,则这一轮获胜;否则,这一轮失败。
埃戈尔对纸牌的顺序了如指掌。请帮助埃戈尔确定他在这样的游戏中最多可以赢多少局。请注意,伊戈尔不需要连续赢得回合。
分析:
本题我们考虑贪心,使用双指针先一直往后加和(后面的指针往后走),当和不小于 l l l时,将前指针也往后走,假如现所在位置为 l l l。那么就将 s u m − a l sum−a_l sum−al。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 1e6 + 5;
LL a[N];
void solve() {
int n, L, R;
cin >> n >> L >> R;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
LL cnt = 0;
LL sum = 0;
int l = 0;
for (int r = 1; r <= n; r++) {
sum += a[r];
while (l + 1 <= r && sum > R) {
sum -= a[++l];
}
if (sum >= L && sum <= R) {
cnt++;
sum = 0;
l = r;
}
}
cout << cnt << endl;
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
D.Beauty of the mountains(数学)
题意:
尼基塔喜欢山,终于决定去贝里安德山脉看看!这座山脉如此美丽,尼基塔决定用地图记录下来。地图是一个由 n n n行和 m m m列组成的表格,每个单元格都包含一个非负整数,代表山的高度。
他还注意到,山有两种类型:
- 有雪盖。
- 无雪帽。
尼基塔是一个非常务实的人。他希望有雪帽的山的高度总和等于没有雪帽的山的高度总和。他已经和贝里安德的市长波利卡普-波利卡波维奇达成了协议,允许他改造地貌。
尼基塔可以对大小为 k × k k\times k k×k的子矩阵进行如下变换:他可以在该区域内的山峰高度上添加一个整数常数 c c c,但山峰的类型保持不变。尼基塔可以为每次变换独立选择常数 c c c。注意 c c c可以是负数。
在进行变换之前,尼基塔会要求您找出是否有可能实现总和相等,或者是否不可能。即使山变成了峡谷,高度为负数,代价也无所谓。
如果地图上只有一种类型的山,那么另一种类型的山的高度之和将被视为零。
分析:
我们考虑到,如果要让这两部分分别相等( 0 0 0部分的和为 s u m 0 sum_0 sum0, 1 1 1部分的和为 s u m 1 sum_1 sum1),那么我们对于每一个 k × k k×k k×k的矩阵 m i m_i mi,假设当前矩阵被选且其中有 n u m i , 0 num_{i,0} numi,0个 0 0 0,和 n u m i , 1 num_{i,1} numi,1个 1 1 1,且当前答案系数为 c i c_i ci。
我们令
s
u
m
0
sum_0
sum0加上
c
k
×
n
u
m
i
,
0
c_k×num_{i,0}
ck×numi,0,
s
u
m
1
sum_1
sum1加上
c
k
×
n
u
m
i
,
1
c_k×num_{i,1}
ck×numi,1,那么
s
u
m
0
sum_0
sum0会加上
∑
c
i
×
n
u
m
i
,
0
(
i
∈
S
)
\sum c_i×num_{i,0}(i∈S)
∑ci×numi,0(i∈S),
s
u
m
1
sum_1
sum1会加上
∑
c
i
×
n
u
m
i
,
1
(
i
∈
S
)
\sum c_i×num_{i,1}(i∈S)
∑ci×numi,1(i∈S)。这两部分加上原来的部分就是我们要求的相等的和,所以设原来两部分的差为
m
n
mn
mn,这样则有
∑
c
i
×
∣
n
u
m
i
,
0
−
n
u
m
i
,
1
∣
=
m
n
(
i
∈
S
)
\sum c_i×∣num_{i,0}−num_{i,1}∣=mn(i∈S)
∑ci×∣numi,0−numi,1∣=mn(i∈S)。
而绝对值内的部分可以通过二维前缀和预处理, c c c为未知数,所以这是不定方程的形式,我们利用裴蜀定理,求出所有 ∣ n u m i , 0 − n u m i , 1 ∣ ∣num_{i,0}−num_{i,1}∣ ∣numi,0−numi,1∣的 g c d gcd gcd(如果其中存在 0 0 0则跳过),如果所有这些数都是 0 0 0且 m n = 0 mn=0 mn=0,或者这个 g c d gcd gcd整除 m n mn mn则有方案,否则无方案。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 5e2 + 2;
LL gcd(int a, int b) {
return b > 0 ? gcd(b, a % b) : a;
}
LL n, m, k, a[N][N], c[N][N];
string s[N];
LL cal1(int x1, int y1, int x2, int y2) {
return c[x2][y2] - c[x1 - 1][y2] - c[x2][y1 - 1] + c[x1 - 1][y1 - 1];
}
LL cal2(int x1, int y1, int x2, int y2) {
return a[x2][y2] - a[x1 - 1][y2] - a[x2][y1 - 1] + a[x1 - 1][y1 - 1];
}
void solve() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
for (int i = 1; i <= n; i++) {
cin >> s[i];
s[i] = " " + s[i];
for (int j = 1; j <= m; j++) {
if (s[i][j] == '0') {
c[i][j] = -1;
a[i][j] *= -1;
} else {
c[i][j] = 1;
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
c[i][j] += c[i - 1][j] + c[i][j - 1] - c[i - 1][j - 1];
a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
}
}
LL g = 0;
for (int i = 1; i + k - 1 <= n; i++) {
for (int j = 1; j + k - 1 <= m; j++) {
LL sum = cal1(i, j, i + k - 1, j + k - 1);
sum = abs(sum);
g = gcd(g, sum);
}
}
LL sum = cal2(1, 1, n, m);
if ((g == 0 && sum == 0) || (g != 0 && sum % g == 0)) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
E.Number of k-good subarrays(思维)
题意:
让 b i t ( x ) bit(x) bit(x)表示非负整数 x x x的二进制表示中的 1 1 1的个数。
如果一个数组的子数组只由二进制表示中的 1 1 1数不超过 k k k的数组成,那么这个数组被称为 k k k(好)数组,对于数组 a a a的子数组 ( l , r ) (l,r) (l,r),当满足条件 b i t ( a i ) ≤ k bit(a_i)≤k bit(ai)≤k对于所有 l ≤ i ≤ r l≤i≤r l≤i≤r的 i i i时,则该子数组是 k k k好的。
给你一个长度为 n n n的数组 a a a,由从 0 0 0开始的连续的非负整数组成,即 0 ≤ i ≤ n − 1 0\le i\le n-1 0≤i≤n−1的 a i = i a_{i}=i ai=i。你需要计算这个数组中 k k k好的子数组的数量。
由于答案可能非常大,所以请输出对 1 0 9 + 7 10^{9}+7 109+7取模的结果。
分析:
设 s o l ( n , k ) sol(n,k) sol(n,k)为答案。(注意,是 [ 0 , n − 1 ] [0,n-1] [0,n−1]的)那么 s o l ( n , k ) sol(n,k) sol(n,k)可以通过 s o l ( m x , k ) sol(mx,k) sol(mx,k)和 s o l ( n − m x , k − 1 ) sol(n-mx,k-1) sol(n−mx,k−1)算出来,其中 m x mx mx是小于 n n n的最大 2 2 2次幂。(大于等于 m x mx mx的就会有一位固然是 1 1 1,就会是 k − 1 k-1 k−1。)设 m x = 2 c mx=2^c mx=2c。
除了内部的,还有一个端点在左边一个端点在右边的。官方题解是维护了三个值,较为复杂。
我们发现,如果 c > k c\gt k c>k,那么左边最后一个就会有大于 k k k个 1 1 1,没有贡献,所以必须 c ≤ k c\le k c≤k。这个时候左边所有的都满足,只需要计算右边的。右边的到了 2 k − 1 2^k-1 2k−1也不行,因为这样也超过了(当然要和长度取最小值)。所以我们算出了右边的贡献 s = m i n ( 2 c − 1 , n − 2 c ) s=min(2^c-1,n-2^c) s=min(2c−1,n−2c)。那么,答案就要多加上 s × 2 c s\times 2^c s×2c。直接记忆化搜索即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL MOD = 1e9 + 7;
map<pair<LL, LL>, LL> mp;
LL sol(LL n, LL k) {
if (k == 0 || n == 1) {
return 1;
}
if (mp.count({n, k})) {
return mp[{n, k}];
}
LL c = 0;
while ((1ll << c + 1) < n) {
c++;
}
LL l = sol(1ll << c, k);
LL r = sol(n - (1ll << c), k - 1);
LL ans = l + r;
if (c <= k) {
LL s = min((1ll << k) - 1, n - (1ll << c));
ans += s % MOD * ((1ll << c) % MOD) % MOD;
}
mp[{n, k}] = ans % MOD;
return ans % MOD;
}
void solve() {
LL n, k;
cin >> n >> k;
cout << sol(n, k) << endl;
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。