Codeforces Round #623 (Div. 1)
Codeforces Round #623 (Div. 1)
A
略
B
胜者组的比赛是满二叉树的结构,按照常规方式给节点编号:\(x\) 的左儿子为 \(2x\),右儿子为 \(2x+1\)。设 \(f_{i,0/1,0/1}\) 表示以 \(i\) 为根的子树中,剩下的胜者和败者是不是关键人物。然后正常转移就好了。
C
先将所有的子串排序。预处理出 \(L_{i,j}\) 表示后缀 \([i,n]\) 和 \([j,n]\) 的最长公共前缀,可 \(O(n^2)\) 递推。对于两个子串,若最长公共前缀未超出长度,比较下一个字符大小,否则比较长度,比较一次 \(O(1)\)。
然后二分答案,计算所有划分都 \(\ge mid\) (\(mid\) 为排名)的方案数是否 \(\ge k\)。用 \(f_{i,j}\) 表示前 \(i\) 个划分成 \(j\) 段且都 \(\ge mid\) 的方案数,但由于字典序是从前往后比较的,这样转移需要枚举最后一段的开头一个一个进行判断,复杂度 \(O(n^3)\)。于是可以从后往前,这样开头是定的,字典序大小有单调性,找到第一个可行的位置(满足 \(\ge mid\)),后面的都可以用,弄个后缀和就好了,\(O(n^2log\ n)\)
int n, m, L[N][N], rk[N][N];
ll k, f[N][N], s[N][N]; char a[N];
struct sub {
int l, r;
bool operator < (const sub &x) const {
int p = l + L[l][x.l], q = x.l + L[l][x.l];
return p <= r && q <= x.r ? a[p] < a[q] : r - l < x.r - x.l;
}
} p[N * N / 2];
void qwq (ll &x) { if (x > k) x = k + 1; }
void print (int x) {
for (int i = p[x].l; i <= p[x].r; ++i) putchar (a[i]);
putchar ('\n');
}
int check (int x) {
memset (s, 0, sizeof (s));
memset (f, 0, sizeof (f));
s[n + 1][0] = 1;
for (int i = n, j; i >= 1; --i) {
for (j = i; j <= n; ++j) if (rk[i][j] >= x) break;
for (int u = 1; u <= m; ++u)
f[i][u] += s[j + 1][u - 1], qwq (f[i][u]);
for (int u = 1; u <= m; ++u)
s[i][u] = s[i + 1][u] + f[i][u], qwq (s[i][u]);
s[i][0] = 1;
}
return f[1][m] >= k;
}
signed main() {
scanf ("%d %d %lld %s", &n, &m, &k, a + 1);
for (int i = n; i >= 1; --i)
for (int j = n; j >= 1; --j)
if (a[i] == a[j]) L[i][j] = L[i + 1][j + 1] + 1;
int c = 0;
for (int i = 1; i <= n; ++i)
for (int j = i; j <= n; ++j) p[++c] = {i, j};
sort (p + 1, p + c + 1);
for (int i = 1; i <= c; ++i) rk[p[i].l][p[i].r] = i;
int l = 1, r = c, mid, res = 0;
while (l <= r) {
mid = l + r >> 1;
if (check (mid)) res = mid, l = mid + 1;
else r = mid - 1;
}
return print (res), 0;
}
D
比较麻烦的是奇环的条件。先把图随机二分染色,然后进行 \(dp\)。由于 \(k\) 比较小,所以只需重复较少次数即可得到答案(随机染色方案符合最优解)
E
\(k=1\),就是求:非严格单调上升序列 \(b\) 满足 \(\sum b_i\leq n\),有多少个 \(b\),简单 \(dp\) 即可 \(n^2\) 计算
\(k=2\),求非严格单调下降序列 \(c\) 的数量,但此时限制条件不同。若 \(c\) 存在,那么 \(a\) 的长度至少为 \(len=\sum c_i·b_i\ge\sum c_i·i\)(让大的 \(c_i\) 和小的 \(b_i\) 对应),只需满足 \(\sum c_i·i\leq n\) 即可,用类似的 \(dp\) 求解复杂度为 \(O(n^2log\ n)\)
\(k\ge 3\),\(c\) 最多只有 \(\sqrt{2n}=64\) 个数,\(k\) 越大数量越少,那么此时方案不多,直接 \(dfs+\) 剪枝:按照 \(k=2\) 的最小化法则往前倒推构造 \(k-1\) 轮,弄出最优的 \(b\),判断 \(\sum b_i \leq n\) ,若不行直接 \(return\),复杂度为......
#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 2030, mod = 998244353;
#define up(x, y) ((x += y) >= mod && (x -= mod))
int n, k, res, f[N], dp[N][N];
void work1 () {
f[0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = i; j <= n; ++j) up (f[j], f[j - i]);
for (int i = 1; i <= n; ++i) up (res, f[i]);
}
void work2 () {
dp[0][0] = 1;
for (int i = n; i >= 1; --i)
for (int j = 1; j * i <= n; ++j)
for (int k = i * j; k <= n; ++k)
up (dp[j][k], dp[j - 1][k - i * j]);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) up (res, dp[i][j]);
}
int a[N], b[N], c[N], m, tp;
int check () {
int m = 0, s = 0;
for (int i = 1; i <= tp; ++i) b[++m] = a[i];
for (int t = 1; t < k; ++t) {
s = 0; int mm = 0;
for (int i = 1; i <= m; ++i) s += b[i];
if (s > n) return 0;
for (int i = 1; i <= m; ++i)
for (int j = 1; j <= b[m - i + 1]; ++j) c[++mm] = i;
for (int i = 1; i <= mm; ++i) b[i] = c[i]; m = mm;
}
s = 0; for (int i = 1; i <= m; ++i) s += b[i];
if (s > n) return 0; else return ++res, 1;
}
int work3 (int now, int s) {
if (s && !check ()) return 0;
for (int i = now; i + s <= n; ++i) {
a[++tp] = i;
if (!work3 (i, s + i)) return --tp, 1; --tp;
}
return 1;
}
signed main() {
read (n), read (k);
if (k == 1) work1 ();
else if (k == 2) work2 ();
else work3 (1, 0);
return printf ("%d\n", res), 0;
}
F
题意太长太毒瘤...