比赛链接
官方题解
Problem A. Fairness
由于要求计算 ,初始序列 与 的结果是相同的。
令 ,注意到两轮操作后,序列变为了 ,与初始序列 对应的结果相同,因此将 模 后处理即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
ll a, b, c, k;
int main() {
read(a), read(b), read(c), read(k);
if (k % 2) writeln(b - a);
else writeln(a - b);
return 0;
}
Problem B. Backfront
考虑选定一些数,然后按照一定顺序操作剩余的数进行排序。
显然,能够排序的充要条件为所选定的数构成一个值域连续的上升子序列。
因此,问题等价于计算给定序列的最长值域连续的上升子序列的长度,简单扫描即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int a[MAXN];
int main() {
int n; read(n);
for (int i = 1; i <= n; i++) {
int x; read(x);
a[x] = i;
}
int now = 1, ans = 0;
for (int i = 1; i <= n; i++) {
if (a[i + 1] > a[i]) now++;
else {
chkmax(ans, now);
now = 1;
}
}
writeln(n - ans);
return 0;
}
Problem C. Sequence Growing Easy
不难发现,序列 的各个元素是不会下降的。
因此,最优的方式一定是优先正确地得到靠后的数,从后向前贪心即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int a[MAXN];
int main() {
int n; read(n);
for (int i = 1; i <= n; i++)
read(a[i]);
if (a[1] != 0) {
puts("-1");
return 0;
}
ll ans = 0; int now = 0;
for (int i = n; i >= 1; i--, now = max(0, now - 1)) {
if (now > a[i]) {
puts("-1");
return 0;
}
if (now < a[i]) {
now = a[i];
ans += now;
}
}
writeln(ans);
return 0;
}
Problem D. Isomorphism Freak
记树的直径为 ,则第一问的答案应当为 ,这是因为任意一棵树直径上到两端距离不同的点为根形成的树一定不同构,而这个下界同样可以构造达到。
考虑枚举最终形成的树直径的中心,它可能是一个点,或者一条边,以一个点 为例。
那么对于第一问来说, 为中心的树答案显然为其深度 。
并且,为了达到第一问的最优答案,我们需要将
的每一棵子树、子树的子树等修改得完全同构,因此,令
表示深度为
的节点拥有的最多的子节点数,第二问的答案应当为
在所枚举的中心中取最优者即可。
时间复杂度 ,若只枚举使得第一问取到 的中心,可优化至 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 205;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int ans, d[MAXN]; ll Min;
int n, x[MAXN], y[MAXN];
vector <int> a[MAXN];
void work(int pos, int fa, int depth) {
if (fa == 0) chkmax(d[depth], (int) a[pos].size());
else chkmax(d[depth], (int) a[pos].size() - 1);
for (auto x : a[pos])
if (x != fa) work(x, pos, depth + 1);
}
int main() {
read(n), ans = n + 1;
for (int i = 1; i <= n - 1; i++) {
read(x[i]), read(y[i]);
a[x[i]].push_back(y[i]);
a[y[i]].push_back(x[i]);
}
for (int i = 1; i <= n; i++) {
memset(d, 0, sizeof(d));
work(i, 0, 1);
int now = 1; ll cnt = 1;
for (int i = 1; i <= n; i++)
if (d[i]) {
now++;
cnt *= d[i];
} else break;
if (now < ans) ans = now, Min = cnt;
else if (now == ans) chkmin(Min, cnt);
}
for (int i = 1; i <= n - 1; i++) {
memset(d, 0, sizeof(d));
work(x[i], y[i], 1);
work(y[i], x[i], 1);
int now = 1; ll cnt = 1;
for (int i = 1; i <= n; i++)
if (d[i]) {
now++;
cnt *= d[i];
} else break;
if (now < ans) ans = now, Min = 2 * cnt;
else if (now == ans) chkmin(Min, 2 * cnt);
}
printf("%d %lld\n", ans, Min);
return 0;
}
Problem E. Sequence Growing Hard
考虑序列数组构造的方式,我们可以看做每一次向已有序列中插入一个数,同时要求新形成的序列字典序大于当前序列,问这样的过程数。注意在不同的位置插入后形成相同的序列算作一种插入方式。
由于要求新形成的序列字典序大于当前序列,在某一位置插入的数需要大于该位置上原有的数。因为若小于,显然已经不符合条件了,若等于,可以看做在更靠后的位置插入了该数。
因此,较大的数的插入对较小的数是没有影响的,考虑按照最终序列中所有元素的大小顺序进行 dp 。
记 表示 时原问题的答案。
对于转移,考虑枚举最终序列中
的个数
,则有
其中
是一个只与
有关的转移系数,表示在长度为
的序列中新插入
个更大的数的方案数,有转移
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 305;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, m, P;
int dp[MAXN][MAXN], coef[MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
read(n), read(m), read(P);
dp[0][0] = coef[0][0] = 1;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= n; j++) {
if (i != 0) update(coef[i][j], coef[i - 1][j]);
if (j != 0) update(coef[i][j], 1ll * coef[i][j - 1] * (i + 1) % P);
}
for (int i = 1; i <= m; i++)
for (int j = 0; j <= n; j++) {
for (int k = 0; k <= j; k++)
update(dp[i][j], 1ll * coef[j - k][k] * dp[i - 1][j - k] % P);
}
writeln(dp[m][n]);
return 0;
}
Problem F. Simple Subsequence Problem
考虑对于所有长度在 以内的字符串 ,计算出 表示 是多少给定字符串的子序列。一旦计算出 ,剩余的问题就很简单了。
为此,考虑构造具有如下性质的一张 DAG :
、每一个给定字符串对应一个黑色节点,且出度为 0
、每一个长度在
以内的字符串对应一个白色节点
、还有一些其余的灰色节点
、
对应的白色节点能够到达给定字符串
对应的黑色节点当且仅当
是
的子序列
如果能够构造出上述 DAG ,便只需要进行简单 dp 即可计算出 。
考虑一个字符串 的所有子序列,为了方便说明,以字符串 110001 为例
记 [110001] 表示 110001 的所有子序列的集合,考虑子序列的第一个字符是 0 还是 1 ,有
[110001] = 空串 + 1[10001] + 0[001]
即找到对应字符第一次出现的地方分治下去。
由此,我们可以构造出一个 个点的满足上述性质的 DAG 。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 25;
const int MAXS = 1 << 21;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
char s[MAXS];
int n, k, dp[MAXN][MAXS];
int main() {
read(n), read(k);
for (int i = 0, bit = 1 << i; i <= n; i++, bit <<= 1) {
scanf("\n%s", s);
for (int j = 0; j <= bit - 1; j++)
dp[i][j + bit] = s[j] == '1';
}
for (int i = n, bit = 1 << i; i >= 1; i--, bit >>= 1) {
for (int j = i, bjt = 1 << j; j <= n; j++, bjt <<= 1)
for (int k = 0; k <= bjt - 1; k++) {
int tmp = dp[i][k + bjt], p = -1, q = -1;
for (int t = i; t >= 1; t--)
if (k & (1 << (t - 1))) {
p = t;
break;
}
for (int t = i; t >= 1; t--)
if ((k & (1 << (t - 1))) == 0) {
q = t;
break;
}
dp[0][(k + bjt) >> i] += tmp;
int s = k & ((1 << i) - 1), t = k >> i;
if (p != -1) dp[p - 1][(s & ((1 << (p - 1)) - 1)) + ((2 * t + 1) << (p - 1)) + (bjt >> (i - p))] += tmp;
if (q != -1) dp[q - 1][(s & ((1 << (q - 1)) - 1)) + ((2 * t) << (q - 1)) + (bjt >> (i - q))] += tmp;
}
}
for (int i = n, bit = 1 << i; i >= 0; i--, bit >>= 1) {
for (int j = 0; j <= bit - 1; j++)
//printf("%d %d %d\n", i, j, dp[0][j + bit]);
if (dp[0][j + bit] >= k) {
for (int k = i - 1; k >= 0; k--)
if (j & (1 << k)) putchar('1');
else putchar('0');
puts("");
return 0;
}
}
assert(false);
return 0;
}