A. Farmer John’s Challenge (模拟)
题意:
构造一个长度为 n n n的数组,将这些数组围成一个圈(顺时针)从任意一个位置打开,有且仅有 k k k个非降序排列的数组。
分析:
k = 1 k=1 k=1时,升序输出 1 − n 1-n 1−n, n = k n=k n=k时,全 1 1 1即可。其他情况都是 − 1 -1 −1。
代码:
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
int main() {
int T;
cin >> T;
while (T--) {
int n, k;
cin >> n >> k;
if (k == 1) {
for (int i = 1; i <= n; i++)
cout << i << " ";
cout << endl;
} else if (n == k) {
for (int i = 1; i <= n; i++)
cout << 1 << " ";
cout << endl;
} else
cout << -1 << endl;
}
return 0;
}
B.Bessie and MEX (思维)
题意:
给出一个长度为 n n n 的数组 a a a ,根据 a a a 构造出一个排列 p p p 。数组 a a a 的构造使得 a i a_i ai = MEX ( p 1 , p 2 , … , p i ) − p i \texttt{MEX}(p_1, p_2, \ldots, p_i) - p_i MEX(p1,p2,…,pi)−pi ,其中数组的 MEX \texttt{MEX} MEX 是该数组中没有出现的最小非负整数。
分析:
a i > 0 a_i>0 ai>0,填入当前的最小值,同时更新最小值, a i < 0 a_i<0 ai<0时, m e x mex mex没有更新,加上绝对值即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, mod = 1e9 + 7;
int a[N], p[N];
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
int minval = 0;
for (int i = 1; i <= n; i++) {
if (a[i] > 0) {
p[i] = minval;
minval = a[i] + minval;
} else {
p[i] = minval - a[i];
}
}
for (int i = 1; i <= n; i++)
cout << p[i] << " ";
cout << endl;
}
return 0;
}
C1. Bessie’s Birthday Cake (Easy Version) (数学)
题意:
贝西从她最好的朋友埃尔西那里收到了一个生日蛋糕,它是一个边长为
n
n
n 的正多边形。蛋糕的顶点按顺时针方向从
1
1
1 排列到
n
n
n 。你和贝西将选择其中的一些顶点在蛋糕上切出不相交的对角线。换句话说,对角线的端点必须是所选顶点的一部分。
为了保持一致性,贝西只想分发三角形的蛋糕。蛋糕块的大小并不重要,整个蛋糕也不一定要分成三角形(蛋糕中可以有其他形状,但不计算在内)。
贝西已经选择了
x
x
x 个顶点,可以用来组成对角线。她希望你选择的其他顶点不超过
y
y
y ,这样她能分出的三角形蛋糕的数量就能达到最大。
贝西最多能分出多少块三角形蛋糕?
此题保证
y
=
0
y=0
y=0
分析:
假设对答案有贡献的点叫有效点,可以发现如果两个点之间正好只有一个点,那么这个点也是有效的,答案是总的有效点数减 2 2 2.
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, mod = 1e9 + 7;
int a[N];
int main() {
int T;
cin >> T;
while (T--) {
int n, x, y, cnt;
cin >> n >> x >> y;
cnt = x - 2;
for (int i = 1; i <= x; i++)
cin >> a[i];
sort(a + 1, a + x + 1);
for (int i = 1; i < x; i++) {
if (a[i] + 1 == a[i + 1] - 1) {
cnt++;
}
}
if (a[x] == n - 1 && a[1] == 1)
cnt++;
else if (a[x] == n && a[1] == 2)
cnt++;
cout << cnt << endl;
}
return 0;
}
C2.Bessie’s Birthday Cake (Hard Version) (数学)
题意:
贝西从她最好的朋友埃尔西那里收到了一个生日蛋糕,它是一个边长为
n
n
n 的正多边形。蛋糕的顶点按顺时针方向从
1
1
1 排列到
n
n
n 。你和贝西将选择其中的一些顶点在蛋糕上切出不相交的对角线。换句话说,对角线的端点必须是所选顶点的一部分。
为了保持一致性,贝西只想分发三角形的蛋糕。蛋糕块的大小并不重要,整个蛋糕也不一定要分成三角形(蛋糕中可以有其他形状,但不计算在内)。
贝西已经选择了
x
x
x 个顶点,可以用来组成对角线。她希望你选择的其他顶点不超过
y
y
y ,这样她能分出的三角形蛋糕的数量就能达到最大。
贝西最多能分出多少块三角形蛋糕?
分析:
通过简单版本发现,每次操作可以让有效点数加 2 2 2,但是如果一段只剩下 3 3 3个点,那么可以一次性操作让有效点数加 3 3 3。同时发现奇数间隔可以获得的贡献更大,所以优先操作间距短的奇数长度区间间隔,再操作偶数区间间隔。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, mod = 1e9 + 7;
int main() {
int T;
cin >> T;
while (T--) {
int n, x, y;
cin >> n >> x >> y;
vector<int> a(x);
for (int i = 0; i < x; i++)
cin >> a[i];
sort(a.begin(), a.end());
int sum = x;
vector<int> tmp[2];
for (int i = 0; i < x; i++) {
int len = a[(i + 1) % x] - a[i] - 1;
if (len < 0)
len += n;
if (len == 1)
sum += 1;
if (len > 1) {
tmp[len % 2].push_back(len);
}
}
for (int i = 1; i >= 0; i--) {
sort(tmp[i].begin(), tmp[i].end());
for (auto u: tmp[i]) {
if (u % 2 == 1) {
int num2 = (u - 3) / 2;
int t = min(y, num2);
y -= t;
sum += t * 2;
int num3 = 1;
t = min(y, num3);
y -= t;
sum += t * 3;
} else {
int num2 = u / 2;
int t = min(y, num2);
y -= t;
sum += t * 2;
}
}
}
cout << sum - 2 << endl;
}
return 0;
}
D.Learning to Paint (dp)
题意:
艾尔西正在学习如何绘画。她的画布上有
n
n
n 个单元格,编号从
1
1
1 到
n
n
n ,她可以画任何(可能是空)单元格子集。
艾尔西有一个二维数组
a
a
a ,她将用这个数组来评估绘画作品。假设一幅画中绘画单元格的最大连续间隔为
[
l
1
,
r
1
]
,
[
l
2
,
r
2
]
,
…
,
[
l
x
,
r
x
]
[l_1,r_1],[l_2,r_2],\ldots,[l_x,r_x]
[l1,r1],[l2,r2],…,[lx,rx] 。这幅画的美丽值是所有
1
≤
i
≤
x
1 \le i \le x
1≤i≤x 中
a
l
i
,
r
i
a_{l_i,r_i}
ali,ri 的总和。
有
2
n
2^n
2n 种方法可以绘制条形图。请帮助艾尔西找出在所有这些方法中,前
k
k
k大的美丽值。这
k
k
k种美丽值可以相同,但要保证至少有
k
k
k种画法。
分析:
f
f
f表示使用前
i
i
i个位置的前
k
k
k大和,但是位置
i
i
i不一定要涂色。
g
g
g表示使用前
i
i
i个位置且以
i
i
i开头的前
k
k
k大和,保证位置
i
i
i为涂色开头。即
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示使用前
i
i
i个位置,第
j
j
j大的和。
g
[
i
]
[
j
]
g[i][j]
g[i][j]表示 使用前
i
i
i个位置,
i
i
i涂色 且
i
−
1
i-1
i−1不涂色 第
j
j
j大的和。
维护
f
[
i
]
f[i]
f[i],可以用优先队列,从
g
[
1...
i
]
g[1...i]
g[1...i]转移,同时维护
g
[
1...
i
]
g[1...i]
g[1...i]最大的值,取前
k
k
k个用于更新
f
[
i
]
f[i]
f[i]。在更新
g
[
i
+
1
]
g[i+1]
g[i+1]时,因为
i
i
i不涂色,以
i
−
1
i-1
i−1及前面的位置结尾都可,因此就是
g
[
i
+
1
]
=
f
[
i
−
1
]
;
g[i+1]=f[i-1];
g[i+1]=f[i−1];
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define PII pair<LL, LL>
const int mod = 1e9 + 7;
const int INF = 1e9;
int main() {
int T;
cin >> T;
while (T--) {
int n, k;
cin >> n >> k;
vector<vector<int>> a(n + 1, vector<int>(n + 1));
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
cin >> a[i][j];
}
}
vector<vector<int>> f(n + 1, vector<int>(k, -INF));
vector<vector<int>> g(n + 2, vector<int>(k, -INF));
g[1][0] = 0;
f[0][0] = 0;
vector<int> c(k);
vector<int> p(n + 1);
for (int i = 1; i <= n; i++) {
priority_queue<PII > tmp;
for (int j = 1; j <= i; j++) {
tmp.push({g[j][0] + a[j][i], j});
p[j] = 0;
}
for (int t = 0; t < k; t++) {
auto [s, j] = tmp.top();
tmp.pop();
f[i][t] = s;
p[j]++;
tmp.push({g[j][p[j]] + a[j][i], j});
}
int l = 0, r = 0;
for (int t = 0; t < k; t++) {
c[t] = max(f[i - 1][l], f[i][r]);
if (f[i - 1][l] > f[i][r])
l++;
else
r++;
}
f[i] = c;
g[i + 1] = f[i - 1];
}
for (int i = 0; i < k; i++) {
cout << f[n][i] << " ";
}
cout << endl;
}
return 0;
}
E.Farm Game (组合数)
题意:
农夫
N
h
o
j
Nhoj
Nhoj 带着他的奶牛来到农夫
J
o
h
n
John
John 的农场玩游戏!农夫约翰的农场可以用一条在
0
0
0 和
l
+
1
l + 1
l+1 点有墙的数线来模拟。农场里有
2
n
2n
2n 头奶牛,其中
n
n
n 头属于
F
J
FJ
FJ,另外
n
n
n 头属于
F
N
FN
FN。他们把每头牛都放在一个不同的点上,
F
J
FJ
FJ 的牛和
F
N
FN
FN 的牛都不相邻。如果两头奶牛之间没有其他奶牛,它们就是相邻的。
在一次下棋中,农夫选择了
k
k
k
(
1
≤
k
≤
n
)
(1 \leq k \leq n)
(1≤k≤n) 和一个方向(左或右)。然后,农夫选择
k
k
k 头奶牛,并将它们朝所选方向移动一个位置。农场主不能将自己的奶牛移动到墙上或其他农场主的奶牛上。如果一位农夫不能移动任何奶牛,那么他就输了。
F
J
FJ
FJ先开始游戏,进行第一个回合。
给定
l
l
l 和
n
n
n ,求如果两个农夫都以最佳方式下棋,农夫约翰可能获胜的棋局配置数。对局可能无限期地进行下去,在这种情况下没有农民获胜。如果有任何
i
i
i 与
a
i
a_i
ai 或
b
i
b_i
bi 不同,则该配置与另一种配置不同。输出答案,请将答案对
998244353
998244353
998244353取模。
分析:
首先发现
a
[
i
]
a[i]
a[i]在
b
[
i
]
b[i]
b[i]前面 和
a
[
i
]
a[i]
a[i]在
b
[
i
]
b[i]
b[i]后面这两种情况实际是一样,只需要计算其中一种情况,最后答案乘
2
2
2即可。
如果
b
[
i
]
−
a
[
i
]
−
1
b[i]-a[i]-1
b[i]−a[i]−1 不全为偶数,那么就是
F
J
FJ
FJ 必胜。因为全为偶数是必败态,又由于
k
k
k至少为
1
1
1,如果全是偶数,
F
J
FJ
FJ 转移一次后
F
N
FN
FN 获得的是必胜态。如果不全是偶数,
F
J
FJ
FJ 可以转移一次全变成偶数,
F
N
FN
FN 获得的就是必败态了。
我们逆向考虑,用总答案数减去全为偶数的情况。总答案数为
C
(
l
,
2
×
n
)
C(l,2\times n)
C(l,2×n)。当
(
b
[
i
]
−
a
[
i
]
−
1
)
(b[i]-a[i]-1)
(b[i]−a[i]−1)全为偶数时,考虑空地怎么插在牛之间,
(
a
,
b
)
(a,b)
(a,b)内只能插偶数块空地,枚举插入
(
a
,
b
)
(a,b)
(a,b)内的空地对的个数为
m
m
m。
(
a
,
b
)
(a,b)
(a,b)一共有
n
n
n 组,答案为
C
(
n
+
m
−
1
,
m
−
1
)
C(n+m−1,m−1)
C(n+m−1,m−1)。
剩下的空地插在
(
b
,
a
)
(b,a)
(b,a)和
(
0
,
a
[
0
]
)
(0,a[0])
(0,a[0]),
(
b
[
n
]
,
l
+
1
)
(b[n],l+1)
(b[n],l+1)内,一共
n
+
1
n+1
n+1组,用同样的方法计算。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
const int mod = 998244353;
template<const int T>
struct ModInt {
const static int mod = T;
int x;
ModInt(int x = 0) : x(x % mod) {}
ModInt(long long x) : x(int(x % mod)) {}
int val() { return x; }
ModInt operator+(const ModInt &a) const {
int x0 = x + a.x;
return ModInt(x0 < mod ? x0 : x0 - mod);
}
ModInt operator-(const ModInt &a) const {
int x0 = x - a.x;
return ModInt(x0 < 0 ? x0 + mod : x0);
}
ModInt operator*(const ModInt &a) const { return ModInt(1LL * x * a.x % mod); }
ModInt operator/(const ModInt &a) const { return *this * a.inv(); }
bool operator==(const ModInt &a) const { return x == a.x; };
bool operator!=(const ModInt &a) const { return x != a.x; };
void operator+=(const ModInt &a) {
x += a.x;
if (x >= mod)
x -= mod;
}
void operator-=(const ModInt &a) {
x -= a.x;
if (x < 0)
x += mod;
}
void operator*=(const ModInt &a) { x = 1LL * x * a.x % mod; }
void operator/=(const ModInt &a) { *this = *this / a; }
friend ModInt operator+(int y, const ModInt &a) {
int x0 = y + a.x;
return ModInt(x0 < mod ? x0 : x0 - mod);
}
friend ModInt operator-(int y, const ModInt &a) {
int x0 = y - a.x;
return ModInt(x0 < 0 ? x0 + mod : x0);
}
friend ModInt operator*(int y, const ModInt &a) { return ModInt(1LL * y * a.x % mod); }
friend ModInt operator/(int y, const ModInt &a) { return ModInt(y) / a; }
friend ostream &operator<<(ostream &os, const ModInt &a) { return os << a.x; }
friend istream &operator>>(istream &is, ModInt &t) { return is >> t.x; }
ModInt pow(int64_t n) const {
ModInt res(1), mul(x);
while (n) {
if (n & 1)
res *= mul;
mul *= mul;
n >>= 1;
}
return res;
}
ModInt inv() const {
int a = x, b = mod, u = 1, v = 0;
while (b) {
int t = a / b;
a -= t * b;
swap(a, b);
u -= t * v;
swap(u, v);
}
if (u < 0)
u += mod;
return u;
}
};
using mint = ModInt<mod>;
mint fact[N], invfact[N];
void init() {
fact[0] = invfact[0] = 1;
for (int i = 1; i < N; i++)
fact[i] = fact[i - 1] * i;
invfact[N - 1] = fact[N - 1].inv();
for (int i = N - 2; i; i--)
invfact[i] = invfact[i + 1] * (i + 1);
}
inline mint C(int a, int b) {
if (a < 0 || b < 0 || a < b)
return 0;
return fact[a] * invfact[b] * invfact[a - b];
}
int main() {
init();
int t;
cin >> t;
while (t--) {
int l, n;
cin >> l >> n;
mint ans = C(l, 2 * n);
int k = l - 2 * n;
for (int m = 0; 2 * m <= k; m++) {
mint res = C(n + m - 1, n - 1);
res *= C((n + 1) + (k - 2 * m) - 1, (n + 1) - 1);
ans -= res;
}
ans *= 2;
cout << ans << endl;
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。