AtCoder Grand Contest 046 – 解题报告

A – Takahashikun, The Strider

SB 题,不解释。

// A.cpp
#include <bits/stdc++.h>

using namespace std;

int x;

int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }

int main()
{
    scanf("%d", &x), printf("%d\n", 360 / gcd(x, 360));
    return 0;
}

B – Extension

不难,直接容斥即可。

// B.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 3030, mod = 998244353;

int n, m, a, b, dp[MAX_N][MAX_N];

int main()
{
    scanf("%d%d%d%d", &a, &b, &n, &m);
    dp[a][b] = 1;
    for (int i = a; i <= n; i++)
        for (int j = b; j <= m; j++)
            if (i != a || j != b)
                dp[i][j] = (0LL + 1LL * dp[i - 1][j] * j % mod + 1LL * dp[i][j - 1] * i % mod + mod - 1LL * dp[i - 1][j - 1] * (i - 1) % mod * (j - 1) % mod) % mod;
    printf("%d\n", dp[n][m]);
    return 0;
}

C – Shift

考虑把 0 看作分隔符,然后可以把操作看成把一个段内的 1 放到另外一个段里面然后造出一个新的字符串。这个东西可以考虑 DP,设置 dp[i][j][k] 为到前 ​\(i\) 个单元、放了 \(j\)​ 个\(1\)​ 且操作次数为 \(k\)​。考虑暴力转移 + 剪枝,就能过了。

// C.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 330, mod = 998244353;

int threshold, len, n, pre[MAX_N], dp[MAX_N][MAX_N][MAX_N], units[MAX_N];
char str[MAX_N];

int main()
{
    scanf("%s%d", str + 1, &threshold), len = strlen(str + 1);
    int last_pos = 0, ones = 0;
    for (int i = 1; i <= len; i++)
        ones += str[i] == '1';
    for (int i = 1; i <= len + 1; i++)
        if (str[i] != '1')
            units[++n] = i - last_pos - 1, last_pos = i;
    for (int i = 1; i <= n; i++)
        pre[i] = pre[i - 1] + units[i];
    dp[0][0][0] = 1, threshold = min(threshold, ones);
    for (int i = 0; i < n; i++)
        for (int j = pre[i]; j <= pre[n]; j++)
            for (int k = 0; k <= threshold; k++)
                if (dp[i][j][k])
                {
                    for (int g = max(j, pre[i + 1]); g <= pre[n]; g++)
                        if (k + g - j - units[i + 1] <= threshold)
                        {
                            if (i + 1 == n && g - j > units[i + 1])
                                break;
                            int cost = g - j <= units[i + 1] ? 0 : g - j - units[i + 1];
                            dp[i + 1][g][k + cost] = (0LL + dp[i + 1][g][k + cost] + dp[i][j][k]) % mod;
                        }
                }
    int ans = 0;
    for (int i = 0; i <= threshold; i++)
        ans = (0LL + ans + dp[n][ones][i]) % mod;
    printf("%d\n", ans);
    return 0;
}

D – Secret Passage

大概是能想出来的,但是写起来有点麻烦。 先定下终串的长度 \(|T|\),然后在前 \(|S| – |T|\) 个字符中选一半留下、一半抛弃(前两个中有一个必须要选,而且必须要合法),然后再找一个 \(|T| – \frac{|S| – |T|}{2}\) 的后缀当作子序列插到 \(T\) 里。然后求本质不同的串。 那么,我们先求一个方案数:有 \(j\) 个额外的 \(0\) 和 \(k\) 个额外的 \(1\),然后有 \(i\) 后缀作子序列。求完这个之后,我们还需要判合法性。 判断合法性,我们可以维护一个 vis 数组来记录前 \(i\) 个字符被抽走了 \(j\) 个 \(0\)、\(k\) 个 \(1\) 之后还剩多少个字符给剩下的 \(|S|-(i-j-k)\) 的前缀。如果有余(指大于等于 0),那么就是合法的。

// D.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 330, mod = 998244353;

int n, dp[MAX_N][MAX_N][MAX_N], vis[MAX_N][MAX_N][MAX_N];
bool verdict[MAX_N][MAX_N][MAX_N];
char str[MAX_N];

int main()
{
    scanf("%s", str + 1), n = strlen(str + 1);
    dp[n][0][0] = 1;
    for (int i = n; i >= 1; i--)
        for (int j = 0; j + (n - i) <= n - 1; j++)
            for (int k = 0; j + k + (n - i) <= n - 1; k++)
                if (str[i] == '0')
                {
                    dp[i - 1][j][k] = (0LL + dp[i - 1][j][k] + dp[i][j][k]) % mod;
                    dp[i][j][k + 1] = (0LL + dp[i][j][k + 1] + dp[i][j][k]) % mod;
                }
                else
                {
                    dp[i - 1][j][k] = (0LL + dp[i - 1][j][k] + dp[i][j][k]) % mod;
                    dp[i][j + 1][k] = (0LL + dp[i][j + 1][k] + dp[i][j][k]) % mod;
                }
    memset(vis, -1, sizeof(vis)), vis[0][0][0] = 0;
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= (i >> 1); j++)
            for (int k = 0; j + k <= (i >> 1); k++)
                if (vis[i][j][k] != -1)
                {
                    int c = vis[i][j][k];
                    if (i + 1 <= n && c != 0)
                    {
                        if (str[i + 1] == '0')
                            vis[i + 1][j + 1][k] = max(vis[i + 1][j + 1][k], c - 1);
                        if (str[i + 1] == '1')
                            vis[i + 1][j][k + 1] = max(vis[i + 1][j][k + 1], c - 1);
                    }
                    if (i + 2 <= n)
                    {
                        if (str[i + 1] == '0' || str[i + 2] == '0')
                            vis[i + 2][j + 1][k] = max(vis[i + 2][j + 1][k], c);
                        if (str[i + 1] == '1' || str[i + 2] == '1')
                            vis[i + 2][j][k + 1] = max(vis[i + 2][j][k + 1], c);
                        vis[i + 2][j][k] = max(vis[i + 2][j][k], c + 1);
                    }
                }
    for (int i = 0; i <= n; i++)
        for (int j = n; j >= 0; j--)
            for (int k = n; k >= 0; k--)
            {
                verdict[i][j][k] = vis[i][j][k] != -1;
                if (i > 0)
                    verdict[i][j][k] |= verdict[i - 1][j][k];
                verdict[i][j][k] |= verdict[i][j + 1][k] || verdict[i][j][k + 1];
            }
    int ans = 0;
    for (int i = 0; i <= n; i++)
        for (int j = 0; j + (n - i) <= n; j++)
            for (int k = 0; j + k + (n - i) <= n; k++)
                if (dp[i][j][k] && (n - i) + j + k != 0 && verdict[i][j][k])
                    ans = (0LL + ans + dp[i][j][k]) % mod;
    printf("%d\n", ans);
    return 0;
}

E – Permutation Cover

这个题很好啊。

我们首先要知道,有解需要满足 \(2\min\{a_i\} \geq \max\{a_i\}\),考虑在出现最大值的分界点会出现三个相同的数,这样就 GG 了。

如果有解,我们可以先考虑构造第一个前缀出来,这样我们后面就可以贪心的去拼接了。每次我们枚举拼在上一个段后面的段长,然后找字典序最小的。如果我们在 \(a_i\) 中先扣除掉这个前缀的频数,然后再来判:如果满足 \(2\min\{a_i\} \geq \max\{a_i\}\),那我们可以先直接 1、2、3、4 这样去搞,这样字典序最小且对 \(a_i\) 只有 1 的扣除。这样下去会出现 \(2\min\{a_i\} + 1 = \max\{a_i\}\) 的情况,然而这种情况下也是有解法的:我们可以考虑让 \(a_i\) 最大的 \(i\) 排在让 \(a_j\) 最小的 \(j\) 前面,然后再和剩下的那些项去归并。

// E.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 1050;

int n, sum, ai[MAX_N], res[MAX_N], candid[MAX_N], pos[MAX_N];
int upper, lower, sa[MAX_N], sb[MAX_N], ans[MAX_N], ansptr;

bool compare(const int &x, const int &y)
{
    if ((ai[x] == lower) == (ai[y] == lower))
        return x < y;
    else
        return (ai[x] == lower) < (ai[y] == lower);
}

void solve(int len)
{
    bool found = false;
    for (int i = 1; i <= len; i++)
        ai[pos[i]]--;
    int min_val = *min_element(ai + 1, ai + 1 + n), max_val = *max_element(ai + 1, ai + 1 + n);
    if ((min_val << 1) >= max_val)
    {
        for (int i = 1; i <= len; i++)
            res[i] = pos[i];
        sort(res + 1, res + 1 + len), found = true;
    }
    else if (((min_val << 1) | 1) == max_val)
    {
        upper = max_val, lower = min_val;
        int ptra = 1, ptrb = 1, la = 0, lb = 0, ptr = 0;
        for (int i = 1; i <= len; i++)
            if (ai[pos[i]] == min_val || ai[pos[i]] == max_val)
                sa[++la] = pos[i];
            else
                sb[++lb] = pos[i];
        sort(sa + 1, sa + 1 + la, compare), sort(sb + 1, sb + 1 + lb);
        // merge;
        while (ptra <= la && ptrb <= lb)
            if (sa[ptra] < sb[ptrb])
                res[++ptr] = sa[ptra++];
            else
                res[++ptr] = sb[ptrb++];
        while (ptra <= la)
            res[++ptr] = sa[ptra++];
        while (ptrb <= lb)
            res[++ptr] = sb[ptrb++];
        found = true;
        for (int i = len + 1; i <= n; i++)
            if (ai[pos[i]] == min_val)
                found = false;
        found |= (ai[sa[1]] == max_val);
    }
    for (int i = 1; i <= len; i++)
        ai[pos[i]]++;
    if (!found)
        res[1] = 0;
}

bool lexicographicalCompare(int *s1, int *s2)
{
    int ptr = 1;
    while (s1[ptr] == s2[ptr])
        ptr++;
    return s1[ptr] < s2[ptr];
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &ai[i]), sum += ai[i], pos[i] = i;
    if (((*min_element(ai + 1, ai + 1 + n)) << 1) < *max_element(ai + 1, ai + 1 + n))
        puts("-1"), exit(0);
    solve(n);
    for (int i = 1; i <= n; i++)
        ans[++ansptr] = res[i], ai[res[i]]--, pos[i] = res[i];
    while (ansptr < sum)
    {
        memset(candid, 0, sizeof(candid)), candid[1] = n + 1;
        int len = 0;
        for (int i = 1; i <= n; i++)
        {
            solve(i);
            if (res[1] != 0 && lexicographicalCompare(res, candid))
            {
                len = i;
                for (int j = 1; j <= i; j++)
                    candid[j] = res[j];
            }
        }
        for (int i = 1; i + len <= n; i++)
            pos[i] = pos[i + len];
        for (int i = 1; i <= len; i++)
            ans[++ansptr] = pos[n - len + i] = candid[i], ai[candid[i]]--;
    }
    for (int i = 1; i <= ansptr; i++)
        printf("%d ", ans[i]);
    puts("");
    return 0;
}

Leave a Reply

Your email address will not be published. Required fields are marked *