Codeforces Round 728 (Div. 2) 解题报告
对应场次为 CF1541
A. Pretty Permutations
题目大意
有一个长度为 \(n(2 \le n \le 100)\) 的排列 \(a\) 初始 \(a_i = i\)
现在要把 \(a\) 变成不存在任何一个位置使得 \(a_i = i\) 且 \(\sum\limits_{i = 1}^n \left|a_i - i\right|\) 最小 输出构造方案
Solution
一个直观的想法是错一位 遗憾的 假了
通过观察样例 我们有两个发现:
- 可以用2的代价让两个数错排
- 可以用4的代价让三个数错排
所以当 \(n\) 为偶数时 直接两个两个数按2的方案错排
当 \(n\) 为奇数时 先把1 2 3按3的方案错排 剩余的两两按2错排即可
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(ll x) {
char ws[51];
int wt = 0;
do {
ws[++wt] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wt; i; --i) putchar(ws[i]);
}
namespace steven24 {
int T, n;
void main() {
T = read();
while (T--) {
n = read();
if (n & 1) {
printf("3 1 2 ");
for (int i = 2; i * 2 + 1 <= n; ++i) printf("%d %d ", i * 2 + 1, i * 2);
printf("\n");
} else {
for (int i = 1; i * 2 <= n; ++i) printf("%d %d ", i * 2, i * 2 - 1);
printf("\n");
}
}
}
}
int main() {
steven24::main();
return 0;
}
B. Pleasant Pairs
进化了 从div2卡C变成了div2卡B
题目大意
给你一个不重的长度为 \(n(2 \le n \le 2 \times 10^5)\) 的数列 询问其中满足 \(i > j\) 且 \(a_i \times a_j = i + j\) 的二元组 \((i, j)\) 的个数
Solution
首先按值排序
注意到 \(i + j < 2n\) 所以当 \(a_i \times a_j \ge 2n\) 时直接break掉
复杂度为 \(\sum\limits_{i = 1}^n \frac{n}{a_i}\)
因为 \(a_i\) 不重 最坏情况即 \(a_i = i\) 这种情况下的复杂度是 \(\text{O}(n\log V)\) 的
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(ll x) {
char ws[51];
int wt = 0;
do {
ws[++wt] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wt; i; --i) putchar(ws[i]);
}
namespace steven24 {
const int N = 2e5 + 0721;
int n, T;
struct node {
int id, val;
friend bool operator<(node x, node y) {
return x.val < y.val;
}
} a[N];
void main() {
T = read();
while (T--) {
n = read();
for (int i = 1; i <= n; ++i) {
a[i].val = read();
a[i].id = i;
}
sort(a + 1, a + 1 + n);
int ans = 0;
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
if (1ll * a[i].val * a[j].val >= 2 * n) break;
if (1ll * a[i].val * a[j].val == a[i].id + a[j].id) ++ans;
}
}
write(ans), putchar('\n');
}
}
}
int main() {
steven24::main();
return 0;
}
C. Great Graphs
差点BC连着卡 太丢人了
结果BC各因为漏开long long挂了一发 更丢人了
题目大意

Solution
发现 \(d_i \ge 0\) 所以我们先考虑如何加正权边构造最短路
进而发现一条 \(1 \to i \to j\) 的最短路显然优于 \(1 \to j\)
因为此时 \(1 \to i\) 的最短路已经构造好了 第一种方案新增的边权为 \(d_j - d_i\) 而第二种为 \(d_j\)
所以实际上最后构造出来的最短路是一条链
然后考虑在这条链的基础上构造负权边
考虑对于一个点 \(i\) 从它出发的负权边一定指向它的前置节点 并且因为没有负环 所以一条从 \(i \to j\) 的负权边边权最小就是 \(-(d_i - d_j)\)
所以一个 \(i\) 对答案的贡献就是 \(-\sum\limits_{j=1}^{i-1} d_i - d_j\)
让这个东西最小 即让里面的东西最大 所以最好让所有小于 \(d_i\) 的节点 \(j\) 都是它的前置节点
发现我们可以让所有 \(i\) 都做到这件事 直接按 \(d\) 从小到大排序即可
最后还有一个问题 就是如何计算贡献和
设 \(g_i = \sum\limits_{j=1}^{i-1} d_i - d_j\) 通过画图或者推导不难发现 \(g_i = g_{i - 1} + (i - 1) \times (d_i - d_{i - 1})\)
所以最后答案就是 \(d_n - \sum\limits_{i = 2}^n g_i\)
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(ll x) {
char ws[51];
int wt = 0;
if (x < 0) putchar('-'), x = -x;
do {
ws[++wt] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wt; i; --i) putchar(ws[i]);
}
namespace steven24 {
const int N = 1e5 + 0721;
int dis[N];
ll g[N];
int n, T;
void main() {
T = read();
while (T--) {
ll ans = 0;
n = read();
for (int i = 1; i <= n; ++i) dis[i] = read();
sort(dis + 1, dis + 1 + n);
for (int i = 2; i <= n; ++i) {
int d = dis[i] - dis[i - 1];
ans += d;
g[i] = g[i - 1] + 1ll * d * (i - 1);
ans -= g[i];
}
write(ans), putchar('\n');
}
}
}
int main() {
steven24::main();
return 0;
}
D. Tree Array
期望题 但是不是很期望
题目大意
给定一棵 \(n(2 \le n \le 200)\) 个节点的树 使用一下方式构造一个 \(n\) 的排列:
首先等概率选取一个节点染色
然后在所有与染色点相邻的未染色点中等概率选取一个节点染色 重复此过程直到所有节点都被染色
染色的先后顺序即为构造出的序列
问这个序列的期望逆序对数 答案对 \(10^9 + 7\) 取模
Solution
考虑枚举根节点 那么每个点作为根节点的答案之和除以 \(n\) 就是答案
因为 \(n\) 很小 此时仍有约 \(\text{O}(n^2)\) 的复杂度供我们计算答案
所以想到直接枚举点对 不妨设 \(i > j\)
那么 \(i\) 先于 \(j\) 被染色 对答案的贡献就为1 否则为0
所以 \((i, j)\) 的期望贡献就是 \(i\) 先于 \(j\) 被染色的概率
发现这样一件事情:根节点到它们的LCA这段路径被染色是这两个点被染色的共同前置条件
所以我们只需要考虑这两个点到LCA这两段路径即可
考虑LCA被染色之后后续被染的点 发现要么染的点对于这两点到已染点的距离没有影响 要么会让其中一个-1
并且显然让其中一个-1的概率是相等的
由于我们只需要关注这两个点哪个先被染 所以我们只需要考虑相等的让其中一个-1的操作即可
或者换一种说法 问题转化为以下这个问题:
有两个栈 \(a\) 和 \(b\) 每次有 \(p\) 的概率从 \(a\) 的栈顶取走一个元素 \(p\) 的概率从 \(b\) 的栈顶取走一个元素 \(1 - 2p\) 的概率啥都不干 问 \(a\) 先比 \(b\) 取空的概率
无论如何 可以设 \(f_{i, j}\) 表示两个点距离已染点的距离(在后面那个说法中即是两个栈中的元素数量)分别为 \(i, j\) 时 \(i\) 被先染(在后面那个说法中即是第一个栈先被取空)的概率
有转移 \(f_{i, j} = (f_{i - 1, j} + f_{i, j - 1}) / 2\)
初始化为 \(f_{0, i} = 1\)
那么枚举点对 \((i, j)\) 且 \(i > j\) 设 \(lca_{i, j} = c\) 它们对答案的期望贡献即为 \(f_{dep_i - dep_c, dep_j - dep_c}\)
总复杂度 \(\text{O}(n^3 \log n)\)
点击查看代码
/*
预处理f数组 初始值f[0][i] = 1
转移式f[i][j] = (f[i - 1][j], f[i][j - 1]) / 2 直接外层循环枚举i内层循环枚举j即可
枚举根节点 然后dfs维护父亲 深度
然后LCA预处理
然后枚举点对 设较大的为i 贡献为f[i->lca][j->lca]
*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(ll x) {
char ws[51];
int wt = 0;
if (x < 0) putchar('-'), x = -x;
do {
ws[++wt] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wt; i; --i) putchar(ws[i]);
}
namespace steven24 {
const int N = 521;
const int mod = 1e9 + 7;
int fa[N][21], dep[N];
ll f[N][N];
int head[N], nxt[N], to[N], cnt;
int n;
ll inv2, ans;
ll ksm(int x, int y) {
ll ret = 1;
while (y) {
if (y & 1) ret = ret * x % mod;
x = 1ll * x * x % mod;
y >>= 1;
}
return ret;
}
inline void add_edge(int x, int y) {
to[++cnt] = y;
nxt[cnt] = head[x];
head[x] = cnt;
}
void dfs(int x, int father) {
dep[x] = dep[father] + 1;
fa[x][0] = father;
for (int i = head[x]; i; i = nxt[i]) {
int y = to[i];
if (y == father) continue;
dfs(y, x);
}
}
void dp() {
inv2 = ksm(2, mod - 2);
for (int i = 1; i <= n; ++i) f[0][i] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
f[i][j] = (f[i - 1][j] + f[i][j - 1]) * inv2 % mod;
}
}
}
void init() {
for (int j = 1; j <= 20; ++j) {
for (int i = 1; i <= n; ++i) fa[i][j] = fa[fa[i][j - 1]][j - 1];
}
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int j = 20; j >= 0; --j) if (dep[fa[x][j]] >= dep[y]) x = fa[x][j];
if (x == y) return x;
for (int j = 20; j >= 0; --j) if (fa[x][j] != fa[y][j]) x = fa[x][j], y = fa[y][j];
return fa[x][0];
}
void main() {
n = read();
for (int i = 1; i < n; ++i) {
int x = read(), y = read();
add_edge(x, y);
add_edge(y, x);
}
dp();
for (int i = 1; i <= n; ++i) {
dfs(i, 0);
init();
for (int j = 1; j <= n; ++j) {
for (int k = 1; k < j; ++k) {
int c = lca(j, k);
ans = (ans + f[dep[j] - dep[c]][dep[k] - dep[c]]) % mod;
}
}
}
write(ans * ksm(n, mod - 2) % mod), putchar('\n');
}
}
int main() {
steven24::main();
return 0;
}
E1. Converging Array (Easy Version)
好乱的题面
题目大意
有一个长度为 \(n\) 的数列 \(a\) (不给定)
给定一个长度为 \(n - 1\) 数列 \(b\)
给定一个长度为 \(n\) 的数列 \(c\) 表示对于 \(a\) 的每一项 都有 \(0 \le a_i \le c_i\)
然后对数列 \(a\) 进行以下操作无数次:

设 \(F(a)\) 表示操作后 \(a\) 的第一项
输入一个数 \(x\) 询问满足 \(F(a) \ge x\) 的原序列 \(a\) 有多少个 对 \(10^9 +7\) 取模
Solution
考虑那个操作 因为后面那个式子是以 \(\frac{a_i + a_{i + 1}}{2}\) 为中心
而前面那个也是以平均数为中心
所以如果取前一项就都取前一项 取后一项就都取后一项
所以它的实际意义就是 如果 \(a_i\) 与 \(a_{i + 1}\) 的差值小于 \(b_i\) 就以平均数为分界线各往旁边稍 \(\frac{b_i}{2}\)
否则不变
因此 \(b\) 的限制实际表示操作完成之后的 \(a\) 序列 \(a_i\) 与 \(a_{i + 1}\) 至少差 \(b_i\)
下一个问题 我们需要考虑如何同时刻画 \(b\) 和 \(c\) 的限制条件
这时发现一个性质 就是无论怎么操作 \(a\) 的前缀和是不变的
所以我们直接用前缀和来同时刻画这两个性质即可
因为给定了结束时 \(a_1\) 的最小值
所以结束时 \(a_2\) 的最小值为 \(a_1 + b_1\) \(a_3\) 的最小值为 \(a_1 + b_1 + b_2\) 以此类推
所以两个限制条件结合我们恰好能刻画出前缀和的上界和下界
设 \(f_{i, j}\) 表示到第 \(i\) 项的前缀和为 \(j\) 的方案数
转移时直接枚举原序列的第 \(i\) 项的取值即可
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(ll x) {
char ws[51];
int wt = 0;
if (x < 0) putchar('-'), x = -x;
do {
ws[++wt] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wt; i; --i) putchar(ws[i]);
}
namespace steven24 {
const int N = 105;
const int mod = 1e9 + 7;
int b[N], c[N];
ll f[N][N * N];
int n, q, x;
void main() {
n = read();
for (int i = 1; i <= n; ++i) c[i] = read();
for (int i = 2; i <= n; ++i) b[i] = read();
q = read(), x = read();
for (int i = 3; i <= n; ++i) b[i] += b[i - 1];
f[0][0] = 1;
int l = 0, r = 0;
for (int i = 1; i <= n; ++i) {
l += x + b[i], r += c[i];
for (int j = max(l, 0); j <= r; ++j) {
for (int k = 0; k <= c[i]; ++k) if (j >= k) f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;
}
}
ll ans = 0;
for (int i = max(l, 0); i <= r; ++i) ans = (ans + f[n][i]) % mod;
write(ans), putchar('\n');
}
}
int main() {
steven24::main();
return 0;
}

浙公网安备 33010602011771号