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;
}
posted @ 2023-01-25 10:32  David24  阅读(222)  评论(0编辑  收藏  举报