AtCoder Beginner Contest 242

A T-shirt 水题

B Minimize Ordering 水题

C 1111gal password

思路

数位DP
设 f[i][j] 表示考虑到前 i 个数位,第 i 个数位上填写的数字为 j 的数的个数
转移方程:$f[i][j]=f[i−1][k], max(1,j−1)≤k≤min(n,j+1)$

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
const int mo = 998244353;
int f[N][10];
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= 9; i++)
        f[1][i] = 1;
    for (int i = 2; i <= n; i++)
        for (int j = 1; j <= 9; j++)
            for (int k = j - 1; k <= j + 1; k++)
            {
                if (k < 1 || k > 9)
                    continue;
                (f[i][j] += f[i - 1][k]) %= mo;
            }
    int ans = 0;
    for (int i = 1; i <= 9; i++)
        (ans += f[n][i]) %= mo;
    cout << ans << endl;
    return 0;
}

D ABC Transform

思路

依次写出进行 1~t 次变换的所有所有变化的话,可以发现,这些变化形成了 n 棵以原字符串的 n 个位置为根节点,深度为 t 的完全二叉树(根节点深度为 0)
每个根节点都对应一段最后生成字符串中的一个长度为 2t 的连续区间(叶节点),因此给出一个 k 我们可以在 O(log2n) 的时间复杂度内,定位编号为 k 的这个叶节点是哪个根节点派生的
考虑从编号为 k 的这个叶子节点往上去寻找从根节点的唯一路径,这条路径就可以推出当前叶子节点的答案。
模拟上述过程,可以发现,k的规模每次减少一半,log2k次计算后,必然为1,这意味着,由于 k 的规模的限制,在开始的很长一段时间内,都是往左子树走的
然后我们发现,往左子树连续走 3 次是没有意义的,因为会重新回到根节点的值,这样每次计算的规模就被我们简化到 3+log2k 次了,这样直接模拟就可以了
时间复杂度为O(|s|+Qlog2k)

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
inline char getleft(char x)
{
    return (x - 'A' + 1) % 3 + 'A';
}
inline char getright(char x)
{
    return (x - 'A' + 2) % 3 + 'A';
}
signed main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    string s;
    cin >> s;
    int q;
    cin >> q;
    while (q--)
    {
        int t, k;
        cin >> t >> k;
        int id;
        if (t >= 60)
            id = 0;
        else
        {
            int left = 1, right = (1ll << t), x = 0;
            while (true)
            {
                if (k >= left && k <= right)
                {
                    id = x;
                    k = k - left + 1;
                    break;
                }
                x++;
                left += (1ll << t);
                right += (1ll << t);
            }
        }
        int tm = 0;
        char now = s[id];
        for (int i = 1; i <= t; i++)
        {
            if (k == 1)
            {
                tm = t - i + 1;
                break;
            }
            if (k & 1)
                now = getleft(now);
            else
                now = getright(now);
            k = (k - 1) / 2 + 1;
        }
        tm %= 3;
        for (int i = 1; i <= tm; i++)
            now = getleft(now);
        cout << now << '\n';
    }
    return 0;
}

E (∀x∀)

思路

考虑回文字符串由前面大约 n/2 个字符翻转拼接而成
若前面这些字符串的字典序已经比原串的字典序小了,那么这个回文串的字典序一定比原串小
问题转化为,前 n/2 个字符构成的字符串,有多少个字符串的字典序比它小,可用数位dp,解决方式类似 C 题
若前 n/2 和原字符串字典序相同,还可能构成 1 个字符串,这样我们需要把这个字符串构造出来,和原串比较字典序即可

代码

#include <bits/stdc++.h>
using namespace std;
const int mo = 998244353;
const int N = 1e6 + 10;
string s;
int n, l;
int f[N][2];
int dfs(int now, bool lim)
{
    if (now == l + 1)
        return 1;
    if (f[now][lim] != -1)
        return f[now][lim];
    int res = 0;
    for (char i = 'A'; i <= (lim ? s[now] : 'Z'); i++)
        (res += dfs(now + 1, lim && i == s[now])) %= mo;
    return f[now][lim] = res;
}
int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        cin >> n;
        cin >> s;
        for (int i = 0; i <= n; i++)
            f[i][0] = f[i][1] = -1;
        l = (s.length() - 1) / 2;
        int ans = dfs(0, 1);
        string t = "";
        if (s.length() & 1)
        {
            for (int i = 0; i <= l; i++)
                t += s[i];
            for (int i = l - 1; i >= 0; i--)
                t += s[i];
        }
        else
        {
            for (int i = 0; i <= l; i++)
                t += s[i];
            for (int i = l; i >= 0; i--)
                t += s[i];
        }
        if (t > s)
        {
            ans = (ans - 1 + mo) % mo;
        }
        cout << ans << endl;
    }
    return 0;
}

F Black and White Rooks

思路

若方案合法,则黑棋和白棋会在不同的行和列上。
设 f[x][y] 表示,b个黑棋放置在x个不同的行,y个不同的列上,不相互攻击的方案数
设g[x][y]表示,w个白棋放置在x个不同的行,y个不同的列上,不相互攻击的方案数
那么最后的答案就是:$\sum_{1≤x1,x2≤n,1≤y1,y2≤m,x1+x2≤n,y1+y2≤m}(n,x1)(n−x1,x2)(m,y1)(m−y1,y2)⋅f[x1][y1]⋅g[x2][y2]$。

考虑如何求 f[i][j] 和 g[i][j],我们以前者为例。

首先,如果在这个 i×j 个格子中任意选择 b 个格子,摆放上黑子,方案数为(i⋅j,b)。

这些方案中,有一些方案,会有空的行或者列,也就意味着实际上的黑子集中在这样一个 p×q 的格子中(1≤p≤i,1≤q≤j,且 p=i 和 q=j 不同时成立)。

这样,原来的问题就化归到若干个规模更小的子问题上。即:
$f[x][y]=(x⋅yb)−∑1≤i≤x,1≤j≤y,(i,j)≠(x,y)f[i][j]$
同理,
$g[x][y]=(x⋅yw)−∑1≤i≤x,1≤j≤y,(i,j)≠(x,y)g[i][j]$
如果我们通过杨辉三角,预处理出 nm×nm 规模的组合数,就可以快速计算组合数的值

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mo = 998244353;
const int N = 55;
int c[N * N][N * N];
int f[N][N], g[N][N];
signed main()
{
    c[0][0] = 1;
    for (int i = 1; i <= 50 * 50; i++)
    {
        c[i][0] = c[i][i] = 1;
        for (int j = 1; j <= 50 * 50; j++)
            c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mo;
    }
    int n, m, b, w;
    cin >> n >> m >> b >> w;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            f[i][j] = c[i * j][b];
            for (int f1 = 1; f1 <= i; f1++)
                for (int f2 = 1; f2 <= j; f2++)
                {
                    if (f1 == i && f2 == j)
                        continue;
                    f[i][j] = ((f[i][j] - f[f1][f2] * c[i][f1] % mo * c[j][f2] % mo) % mo + mo) % mo;
                }
        }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            g[i][j] = c[i * j][w];
            for (int f1 = 1; f1 <= i; f1++)
                for (int f2 = 1; f2 <= j; f2++)
                {
                    if (f1 == i && f2 == j)
                        continue;
                    g[i][j] = ((g[i][j] - g[f1][f2] * c[i][f1] % mo * c[j][f2] % mo) % mo + mo) % mo;
                }
        }
    int ans = 0;
    for (int p1 = 1; p1 <= n; p1++)
        for (int p2 = 1; p1 + p2 <= n; p2++)
            for (int q1 = 1; q1 <= m; q1++)
                for (int q2 = 1; q1 + q2 <= m; q2++)
                {
                    (ans += c[n][p1] * c[n - p1][p2] % mo * c[m][q1] % mo * c[m - q1][q2] % mo * f[p1][q1] % mo * g[p2][q2] % mo) %= mo;
                }
    cout << ans << endl;
    return 0;
}