Educational Codeforces Round 142 (A - F2)
赛时:A B C D
补题:A B C D E F1 F2
Rank: 1199
A
第一种操作只有对两个 \(1\) 作用才会更优。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <cstdlib>
#include <cmath>
#include <deque>
using namespace std;
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
const int N = 5e5 + 10;
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int T, n;
int a[N];
int main()
{
T = read();
while (T--)
{
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
sort(a + 1, a + n + 1);
int cnt = 0;
for (int i = 1; i <= n; i++)
{
if (a[i] > 1) break;
cnt++;
}
cnt = n - cnt + (cnt / 2) + (cnt % 2);
printf("%d\n", cnt);
}
return 0;
}
B
首先用光所有 \(a_1\),然后交替使用 \(a_2, a_3\),最后使用 \(a_4\)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <cstdlib>
#include <cmath>
#include <deque>
using namespace std;
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
const int N = 5e5 + 10;
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int T, a, b, c, d, A, B, ans;
int main()
{
T = read();
while (T--)
{
a = read(), b = read(), c = read(), d = read();
ans = a;
A = a, B = a;
if (a == 0)
{
puts("1");
continue;
}
if (b >= c)
{
ans += c * 2;
b -= c;
if (B < b) ans += B + 1;
else
{
B -= b; ans += b;
ans += min(d, min(A, B) + 1);
}
}
else
{
ans += b * 2;
c -= b;
if (A < c) ans += A + 1;
else
{
A -= c; ans += c;
ans += min(d, min(A, B) + 1);
}
}
printf("%d\n", ans);
}
return 0;
}
C
设 \(pos_i\) 为满足 \(p_{pos_i} = i\) 的位置。
我们将 \(i \in [1, \lfloor \frac{n}{2} \rfloor]\) 与 \(n - i + 1\) 一起考虑,因为最优策略下,一定是 \(i\) 和 \(n - i + 1\) 操作。
首先,如果 \(i\) 操作,那么 \(j \in [1, i)\) 也得操作。
其次,如果 \(pos_i > pos_{n - i + 1}\),那么 \(i\) 需要操作。
最后,如果对于 \(i \in [1, \lceil \frac{n}{2} \rceil]\),\(pos_i < pos_{i - 1}\) 或者 \(pos_{n - i + 1} > pos_{n - i + 2}\),那么 \(i - 1\) 需要操作。
将所有需要操作的数取最大值,就是答案。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <cstdlib>
#include <cmath>
#include <deque>
using namespace std;
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
const int N = 5e5 + 10;
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int T, n, ans;
int p[N], pos[N];
int main()
{
T = read();
while (T--)
{
n = read();
for (int i = 1; i <= n; i++) p[i] = read(), pos[p[i]] = i;
ans = 0;
if (n == 1) {puts("0"); continue;}
if (n == 2)
{
if (pos[1] == 1) puts("0");
else puts("1");
continue;
}
int cur = 0;
for (int i = 1; i <= n / 2; i++)
if (pos[i] > pos[n - i + 1]) cur = i;
for (int i = 1; i <= (n + 1) / 2; i++)
if (pos[i] < pos[i - 1] || pos[n - i + 1] > pos[n - i + 2]) cur = max(cur, i - 1);
printf("%d\n", cur);
}
return 0;
}
D
对于一个 \(p\) 序列,我们要求出是否有 \(q\) 序列满足前 \(k\) 位都有 \(q_{p_i} = i\),相当于要求 \(q\) 的第 \(p_i\) 位为 \(i\)。
这是非常困难的,于是我们考虑反过来算。
已知一个 \(q\) 序列,它对哪些 \(p\) 如何贡献。
设 \(pos_i\) 满足 \(q_{pos_i} = i\),那么相当于要求 \(p_i = pos_i\)。
于是我们枚举 \(j\) 属于 \(1\) 到 \(m\),然后让前缀等于 \(pos_1, pos_2, ..., pos_j\) 的 \(p\) 的序列的答案对 \(j\) 取 \(max\)。
简单哈希加 \(map\) 即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <cstdlib>
#include <cmath>
#include <deque>
using namespace std;
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
const int N = 5e4 + 10, M = 15;
typedef long long ll;
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int T, n, m;
int a[N][M], p[M], pos[M];
map<ll, int> vis[M];
map<ll, int> ans[M];
ll count(int len)
{
ll res = 0;
for (int i = 1; i <= len; i++) res = (res * 10) + (ll)p[i] - 1ll;
return res;
}
int main()
{
T = read();
while (T--)
{
n = read(), m = read();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++) a[i][j] = read(), p[j] = a[i][j];
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++) pos[a[i][j]] = j;
for (int res = 1; res <= m; res++)
{
for (int j = 1; j <= res; j++) p[j] = pos[j];
ans[res][count(res)]++;
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++) p[j] = a[i][j];
int res = 0;
for (int j = 1; j <= m; j++) if (ans[j][count(j)]) res = j;
printf("%d ", res);
}
puts("");
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++) pos[a[i][j]] = j;
for (int res = 1; res <= m; res++)
{
for (int j = 1; j <= res; j++) p[j] = pos[j];
ans[res][count(res)] = 0;
}
}
}
return 0;
}
E
结论:\(\le 10^{18}\) 的数最多有 \(10^5\) 个因子,\(\le 10^9\) 的数最多有 \(10^3\) 个因子。
因此我们首先暴力枚举出 \(m1, m2\) 的所有因子,然后直接做笛卡尔积求出 \(10^6\) 个可能的因子。
排序后去重,接下来考虑对于某一个因子如何算出它最早在第几行出现。
这里有一个复杂度奇奇怪怪的神秘做法。
对于每一个因子 \(i\),它出现的行数一定是它自己的因子,因此也一定是 \(m\) 的因子。
于是我们二分找到最小的满足 \(\lceil \frac{d_i}{d_j} \rceil \le n\) 的 \(j\),然后从 \(j\) 开始向上枚举,直到 \(d_j\) 是 \(d_i\) 的因子或 \(d_j > n\)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <cstdlib>
#include <cmath>
#include <deque>
using namespace std;
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
const int N = 2e6 + 10;
#define int long long
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int T, n, m1, m2, cnt1, cnt2, cnt3, ans, cnt;
int a[N], b[N], c[N];
signed main()
{
T = read();
while (T--)
{
n = read(), m1 = read(), m2 = read();
cnt1 = cnt2 = cnt3 = 0;
ans = cnt = 0;
for (int i = 1; i * i <= m1; i++)
{
if (m1 % i) continue;
a[++cnt1] = i;
if (i * i != m1) a[++cnt1] = m1 / i;
}
for (int i = 1; i * i <= m2; i++)
{
if (m2 % i) continue;
b[++cnt2] = i;
if (i * i != m2) b[++cnt2] = m2 / i;
}
sort(a + 1, a + cnt1 + 1);
sort(b + 1, b + cnt2 + 1);
for (int i = 1; i <= cnt1; i++)
for (int j = 1; j <= cnt2; j++) c[++cnt3] = a[i] * b[j];
sort(c + 1, c + cnt3 + 1);
cnt3 = unique(c + 1, c + cnt3 + 1) - c - 1;
for (int i = 1; i <= cnt3; i++)
{
int l = 1, r = i, mid, x = 0;
while (l <= r)
{
mid = (l + r) >> 1;
if ((c[i] + c[mid] - 1) / c[mid] <= n) r = mid - 1, x = mid;
else l = mid + 1;
}
int flag = 0;
for (int j = x; c[j] <= n; j++)
{
if (c[i] % c[j] == 0)
{
flag = 1;
ans ^= c[j];
cnt++;
break;
}
}
}
printf("%lld %lld\n", cnt, ans);
}
return 0;
}
F1
结论:一个子点集不可能既不蓝联通也不红联通。
证明:
因为整个图为完全图,所以一个子点集及其相关的边也是完全图。
容易发现,保留所有蓝边和保留所有红边互为补图,接下来只需要证明一个完全图自己或者其补图至少有一个联通。
假设在原图中有两个之间没有边的联通块。
那么在补图中这两个联通块之间的所有点对都有连边,也就一定联通。
有了这个结论,我们只需要计算不存在同时蓝联通和红联通的染色方案即可。
显然整个图作为一个子图也是蓝联通/红联通的。
设 \(f_i\) 表示 \(i\) 个点,整个图蓝联通的方案数,\(g_i\) 表示 \(i\) 个点,整个图蓝联通/红联通的方案数。
如何转移?
枚举 \(1\) 号点所在的极大蓝联通块大小 \(j\),那么这种方案对 \(f_i\) 的贡献是 \(f_j g_{i - j} \binom{i - 1}{j - 1}\)。
因为这个联通块是极大的,所以这 \(j\) 个点到其余 \(i - j\) 的所有边都必须是红色的,因此此时算出的是对整个图红联通方案数的贡献。
把红色和蓝色互换后所有子图的联通颜色也互换,所以整个图红联通的方案数等于整个图蓝联通的方案数,这样贡献的是对的。
于是有转移方程:\(f_i = \sum_{j = 1}^{i-1} \limits f_j g_{i-j} \binom{i-1}{j-1}\),\(g_i = 2f_i\)。
容易发现第二条对 \(i = 1\) 不成立,\(f_1, g_1\) 特判即可。
答案是 \(g_n - 2\)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <cstdlib>
#include <cmath>
#include <deque>
using namespace std;
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
const int N = 5e3 + 10, mod = 998244353;
#define int long long
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int n;
int C[N][N], f[N], g[N];
signed main()
{
n = read();
C[0][0] = 1;
for (int i = 1; i <= n; i++)
{
C[i][0] = 1;
for (int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
f[1] = 1, g[1] = 1;
for (int i = 2; i <= n; i++)
{
for (int j = 1; j < i; j++)
f[i] = (f[i] + f[j] * g[i - j] % mod * C[i - 1][j - 1] % mod) % mod;
g[i] = (f[i] * 2) % mod;
}
printf("%lld\n", (g[n] + mod - 2) % mod);
return 0;
}
F2
将 \(F1\) 中的式子拆开:\(B_i = \sum_{j = 1}^{i-1} \limits B_{j}A_{i-j} \binom{i-1}{j-1}\)。这里的 \(B_i\) 对应 \(f_i\),\(A_i\) 对应 \(g_i\)。
\(B_i = \sum_{j=0}^i \limits B_j A_{i-j}\dfrac{(i-1)!}{(i-j)!(j-1)!} = (i-1)!\sum_{j=0}^i \limits \dfrac{B_j}{(j-1)!} \dfrac{A_{i-j}}{(i-j)!}\)。
设 \(C_i = \dfrac{A_i}{i!}, D_i = \dfrac{B_i}{(i-1)!}\)。
\(B_i = (i-1)!\sum_{j=0}^{i} \limits C_{i-j}D_j\)。
这是一个非常漂亮的卷积形式,可惜 \(C_i\) 和 \(D_i\) 都对 \(B_i\) 有依赖性。
考虑根号重构,每 \(B\) 个元素计算一次 \(C\) 和 \(D\) 的卷积。
假设当前计算到 \(i\),上一次卷积的编号在 \(t\),考虑如何利用卷积结果快速计算 \(B_i\)。
容易发现,对于 \(i-j \le t, j \le t\) 的所有 \(j\),\(C_{i-j}D_j\) 的和正好在卷积中计算过,答案记在 \(C\) 与 \(D\) 卷积得到的 \(c\) 数组第 \(i\) 项。
换言之,\(c_i = \sum_{j=i-t}^t \limits C_{i-j}D_j\)。
距离 \(B_i\) 中的求和式只差 \(j < i-t\) 和 \(j > t\) 的部分。
容易发现,这两部分的 \(j\) 最多只有 \(B\) 个,于是我们暴力计算这一部分的贡献即可。
\(B_i = (i-1)!(c_i + \sum_{j=0}^{i-t-1} \limits C_{i-j}D_j + \sum_{j=t+1}^{i} \limits C_{i-j}D_j)\)。
复杂度为 \(O(\dfrac{n^2\log n}{B} + nB)\)。
当 \(B = \sqrt{n \log n}\) 时最优,为 \(O(n\sqrt{n \log n})\)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <cstdlib>
#include <cmath>
#include <deque>
using namespace std;
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
const int N = 2e5 + 10, G = 3, GI = 332748118, mod = 998244353, T = 1000;
#define int long long
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int n, t;
int qpow(int a, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = (res * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return res;
}
void NTT(int len, int * a, int coe)
{
for (int mid = 1; mid < len; mid <<= 1)
{
int Wn = qpow(((coe == 1) ? G : GI), (mod - 1) / (mid << 1));
for (int i = 0; i < len; i += mid << 1)
{
int W = 1;
for (int j = 0; j < mid; j++, W = (W * Wn) % mod)
{
int x = a[i + j], y = (a[i + j + mid] * W) % mod;
a[i + j] = (x + y) % mod;
a[i + j + mid] = (x - y + mod) % mod;
}
}
}
}
int rev[N], c[N];
void mul(int len, int * a, int * b)
{
for (int i = 1; i < len; i++) rev[i] = (rev[i >> 1] >> 1) + ((i & 1) ? (len >> 1) : 0);
for (int i = 0; i < len; i++) if (i < rev[i]) swap(a[i], a[rev[i]]); NTT(len, a, 1);
for (int i = 0; i < len; i++) if (i < rev[i]) swap(b[i], b[rev[i]]); NTT(len, b, 1);
for (int i = 0; i <= len; i++) a[i] = (a[i] * b[i]) % mod;
for (int i = 0; i < len; i++) if (i < rev[i]) swap(a[i], a[rev[i]]); NTT(len, a, -1);
int inv = qpow(len, mod - 2);
for (int i = 0; i <= len; i++) c[i] = (a[i] * inv) % mod;
}
int len;
int A[N], B[N], C[N], D[N], a[N], b[N], inv[N], fac[N];
void init()
{
inv[0] = inv[1] = fac[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = (fac[i - 1] * i) % mod;
for (int i = 2; i <= n; i++) inv[i] = (mod - mod / i) * inv[mod % i] % mod;
for (int i = 1; i <= n; i++) inv[i] = (inv[i - 1] * inv[i]) % mod;
}
signed main()
{
n = read(); init();
len = 1;
A[1] = B[1] = C[1] = D[1] = 1;
for (int i = 2; i <= n; i++)
{
if (t * 2 < i)
{
for (int j = 0; j <= i; j++) B[i] = (B[i] + C[i - j] * D[j] % mod) % mod;
B[i] = (B[i] * fac[i - 1]) % mod;
A[i] = (B[i] * 2) % mod;
C[i] = (A[i] * inv[i]) % mod;
D[i] = (B[i] * inv[i - 1]) % mod;
}
else
{
B[i] = c[i];
for (int j = 0; j < i - t; j++) B[i] = (B[i] + C[i - j] * D[j] % mod) % mod;
for (int j = t + 1; j <= i; j++) B[i] = (B[i] + C[i - j] * D[j] % mod) % mod;
B[i] = (B[i] * fac[i - 1]) % mod;
A[i] = (B[i] * 2) % mod;
C[i] = (A[i] * inv[i]) % mod;
D[i] = (B[i] * inv[i - 1]) % mod;
}
if (i - t == T)
{
while (len <= i * 2) len <<= 1;
for (int i = 0; i <= len; i++) a[i] = C[i], b[i] = D[i];
mul(len, a, b);
t = i;
}
}
printf("%lld\n", (A[n] + mod - 2) % mod);
return 0;
}