牛客周赛 Round 100
A. 小红的双排列
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
for (int i = 1; i <= n; ++ i) {
std::cout << i << " " << i << " ";
}
std::cout << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
B. 小红的双排列拆分
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<int> a(2 * n);
for (int i = 0; i < 2 * n; ++ i) {
std::cin >> a[i];
-- a[i];
}
std::vector<int> st(2 * n), st1(2 * n);
for (int i = 0; i < 2 * n; ++ i) {
if (!st1[a[i]]) {
st1[a[i]] = 1;
st[i] = 1;
}
}
for (int i = 0; i < 2 * n; ++ i) {
if (st[i]) {
std::cout << a[i] + 1 << " ";
}
}
std::cout << "\n";
for (int i = 0; i < 2 * n; ++ i) {
if (!st[i]) {
std::cout << a[i] + 1 << " ";
}
}
std::cout << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
C. 小红的双排列删除
题意:\(2n\)个数,\([1, n]\)每个数出现两次,每次选择一个区间使得端点相同然后删除这个区间的数。求能不能所有数都删掉。
考虑最左边的数,想删掉显然左端点只能从\(1\)开始,那么右端点也固定了,删掉这个前缀后,又变成了同样的问题:我们必须从前面开始删。那么模拟就行,看最后能不能正好删完。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<int> a(2 * n);
for (int i = 0; i < 2 * n; ++ i) {
std::cin >> a[i];
}
for (int i = 0; i < 2 * n; ++ i) {
int j = i + 1;
while (j < 2 * n && a[j] != a[i]) {
++ j;
}
if (j >= 2 * n) {
std::cout << "No\n";
return;
}
i = j;
}
std::cout << "Yes\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
D. 小红的双排列权值
题意:\(2n\)个数,\([1, n]\)每个数出现两次,\(i\)的贡献是其两个数出现的位置的距离减一。你可以交换两个数,求所有数的贡献之和最大。
把两个相同数的贡献看作是一条线段的和。那么交换两个数就是影响到两个线段的长度。记每个数代表线段的左右端点为\(l_i, r_i\),也就是在数组里出现的两个位置。显然只有两个线段没有交集的时候可以多得到\((l_i - r_j) \times 2\)的贡献,其中\(l_i \geq r_j\)。那么我们只需要找到最小的\(r\)和最大的\(l\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<int> a(2 * n);
for (int i = 0; i < 2 * n; ++ i) {
std::cin >> a[i];
-- a[i];
}
std::vector<int> l(n, -1), r(n, -1);
for (int i = 0; i < 2 * n; ++ i) {
if (l[a[i]] == -1) {
l[a[i]] = i;
} else {
r[a[i]] = i;
}
}
i64 sum = 0;
for (int i = 0; i < n; ++ i) {
sum += r[i] - l[i] - 1;
}
i64 ans = sum;
for (int i = 0, j = 2 * n; i < 2 * n; ++ i) {
if (i == r[a[i]]) {
j = std::min(j, i);
}
if (i == l[a[i]]) {
ans = std::max(ans, sum + std::max(0, (i - j) * 2));
}
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
E. 小红的双排列删除得分
题意:\(2n\)个数,\([1, n]\)每个数出现两次,每次选择一个区间使得端点相同然后删除这个区间的数并且得到区间和的价值。求可以得到的最大价值。
题意可以转换为有一些线段,每个线段有价值,选择其中一些使得它们两两没有交集,使得价值和最大。
可以记\(f[i]\)为到\(i\)时的最大价值,有\(f[i] = f[i - 1]\),如果有\(i = r[x]\),则\(f[i] = \max(f[i], f[l[x] - 1] + sum[l[x] ... r[x])\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<int> a(2 * n);
for (int i = 0; i < 2 * n; ++ i) {
std::cin >> a[i];
}
std::vector<int> l(n + 1, -1), r(n + 1, -1);
for (int i = 0; i < 2 * n; ++ i) {
if (l[a[i]] == -1) {
l[a[i]] = i + 1;
} else {
r[a[i]] = i + 1;
}
}
std::vector<i64> sum(2 * n + 1);
for (int i = 0; i < 2 * n; ++ i) {
sum[i + 1] = sum[i] + a[i];
}
std::vector<i64> f(2 * n + 1);
for (int i = 1; i <= 2 * n; ++ i) {
f[i] = f[i - 1];
if (i == r[a[i - 1]]) {
f[i] = std::max(f[i], sum[i] - sum[l[a[i - 1]] - 1] + f[l[a[i - 1]] - 1]);
}
}
std::cout << f[2 * n] << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
F. 小红的双排列期望(easy)
题意:长度为\(2n\)的数组,每个位置有一个概率\(b_i\)。每次选择一个位置这个位置有\(b_i\)概率加一。求构成双排列的最小的操作期望。
显然加到一个同样数的情况下,\(b_i\)越大期望越小。
那么我们从小到大排序,然后从\(k=1\)开始,每次操作\(i\)和\(i+1\)两个位置到\(k\),然后\(i+=2, k+=1\)。这样得到最小操作次数。
如何求具体的期望呢,当概率为\(p\)的情况下,记\(f[i]\)为操作到\(i\)的期望,那么\(f[i] = f[i - 1] \times p + f[i] \times (1 - p) + 1\)。移项得\(f[i] = f[i - 1] + \frac{1}{p}\)。因为\(f[0] = 0\),那么\(f[i] = \frac{i}{p}\)。
代码省略取模类。
点击查看代码
constexpr int P = 1000000007;
using Z = MInt<P>;
void solve() {
int n;
std::cin >> n;
std::vector<int> b(2 * n);
for (int i = 0; i < 2 * n; ++ i) {
std::cin >> b[i];
}
std::ranges::sort(b);
Z ans = 0;
for (int i = 0, k = 1; i < 2 * n; i += 2, ++ k) {
Z x = Z(100) / b[i], y = Z(100) / b[i + 1];
ans += k * (x + y);
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
G. 小红的双排列期望(hard)
题意:和\(F\)相比,有\(1-b_i\)的概率减一,最多减到\(0\)。
一样得到\(f[i] = f[i - 1] \times p + f[i + 1] \times (1 - p) + 1\)。但这个式子似乎无法计算。
改变一下定义,\(f[i]\)表示\(i\)操作到\(n\)的期望,那么\(f[i] = f[i + 1] \times p + f[i - 1] \times (1 - p)\), 则\(f[0] = f[1] \times p + f[0] \times (1 - p) = f[1] + \frac{1}{p}\),继续算:\(f[1] = f[2]\times p + f[0] \times (1-p) + 1 = f[2] \times p + (f[1] + \frac{1}{p})\times(1-p)+1 = f[2] + \frac{1-p}{p^2} + \frac{1}{p}\),继续算可以发现\(f[i] = f[i + 1] + \sum_{j=1}^{i} \frac{(1-p)^{j-1}}{p^j}\)。而\(f[n] = 0\)。那么一层一层代回去,得到\(f[0] = \sum_{i=1}^{n} \sum_{j=1}^{i} \frac{(1-p)^{j-1}}{p^j}\)。
那么我们可以为\(p\in [1, 100]\)做预处理,记\(dp[i][j]\)为概率为\(i\)操作到\(j\)的期望,可以递推计算。注意\(i=100\)时\(dp[i][j] = j\)。
代码省略取模类。
点击查看代码
constexpr int P = 1000000007;
using Z = MInt<P>;
void solve() {
int n;
std::cin >> n;
std::vector<int> b(2 * n);
for (int i = 0; i < 2 * n; ++ i) {
std::cin >> b[i];
}
std::vector f(101, std::vector<Z>(n + 1));
for (int i = 1; i <= 100; ++ i) {
Z p = Z(i) / 100;
Z q = 1 - p;
Z sum = 1 / p;
Z x = 1, y = p;
for (int j = 1; j <= n; ++ j) {
if (i == 100) {
f[i][j] = j;
} else {
f[i][j] = f[i][j - 1] + sum;
x *= q;
y *= p;
sum += x / y;
}
}
}
std::ranges::sort(b);
Z ans = 0;
for (int i = 0, k = 1; i < 2 * n; i += 2, ++ k) {
ans += f[b[i]][k] + f[b[i + 1]][k];
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}

浙公网安备 33010602011771号