Loading

「解题报告」2023-09-24 CSP-S 公开模拟赛

4173: 车牌 (plate)

题目内容

小 \(Y\) 毕业之后来到了车管所工作,他现在掌管着下北泽全市的车牌分配。具体的说,下北泽的车牌是一个长度为 \(5\) 的字符串,字符串的每个字符是一个 \(0−9\) 的数字或者一个 \(A−Z\) 的大写字母。为了避免混淆,每种字符串的车牌最多只能被分配一次,初始没有任何车牌被分配。现在你需要支持以下两种操作:

  • 给定一个长度为 \(5\) 的车牌模板, 每个字符可以是 \(0−9\) 的数字, 一个 \(A−Z\) 的大写字母表示车牌的这个位置必须是该字符, 也可以是三种通配符: 问号 ? 表示可以车牌的这个位置可以是任何字母; 井号 # 表示车牌的这个位置可以是任何数码; 星号 * 表示车牌的这个位置可以是任何字符。你需要查询满足该字符模板的未分配车牌个数。

  • 给定一个长度为 \(5\) 的车牌, 将这个车牌设置为已分配。保证之前该车牌没有被分配过。

输入格式

第一行一个正整数 \(Q\) 表示总操作次数。

接下来 \(Q\) 行形如 \("1 S"\)\("2 S"\) 分别表示第一种操作和第二种操作, \(S\) 对应为车牌模板字符串或者车牌字符串。

输出格式

对于每个第一种操作,输出一行一个正整数表示答案。


原本写了个 trie 树,只有 \(65\) 分,满分做法是借鉴的 Ciaxin 的做法。(%%% Ciaxin)再插入时,对每个数字都替换成 #,对每个字母都替换成 ?,然后再将此字符串插入其中,可以使用 mapunordered_map 来实现。

// The code was written by yifan, and yifan is neutral!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))

template<typename T>
inline T read() {
    T x = 0;
    bool fg = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        fg |= (ch == '-');
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return fg ? ~x + 1 : x;
}

const int N = 2e6 + 5;

struct trie {
    int cnt;
    int ch[N][36];

    trie() {
        cnt = 0;
    }

    int Idn(char c) {
        if (c >= '0' && c <= '9') {
            return c - '0';
        }
        if (c >= 'A' && c <= 'Z') {
            return c - 'A' + 10;
        }
        return -1;
    }

    void Insert(string s) {
        int u = 0, idn;
        for (char c : s) {
            idn = Idn(c);
            if (!ch[u][idn]) {
                ch[u][idn] = ++ cnt;
            }
            u = ch[u][idn];
        }
    }

    int query(int u, string s) {
        int pos = 0, idn, res = 0, len = s.size();
        for (char c : s) {
            if (c == '#' || c == '*') {
                rep (i, 0, 9, 1) {
                    idn = Idn(i + '0');
                    if (!ch[u][idn])    continue ;
                    res += query(ch[u][idn], s.substr(pos + 1, len - pos));
                }
            }
            if (c == '?' || c == '*') {
                rep (i, 0, 25, 1) {
                    idn = Idn(i + 'A');
                    if (!ch[u][idn])    continue ;
                    res += query(ch[u][idn], s.substr(pos + 1, len - pos));
                }
            }
            idn = Idn(c);
            if (idn == -1)    break ;
            if (!ch[u][idn])    break ;
            u = ch[u][idn];
            ++ pos;
        }
        if (pos == len)   ++ res;
        return res;
    }
} T;

int Q;
string s, tmp;
unordered_map<string, int> mp;

void dfs(int u) {
    ++ mp[tmp];
    rep (i, u, 4, 1) {
        tmp[i] = (s[i] >= '0' && s[i] <= '9') ? '#' : '?';
        dfs(i + 1);
        tmp[i] = s[i];
    }
}

int query(int u) {
    bool fg = 1;
    int ans = 0;
    rep (i, u, 4, 1) {
        if (s[i] == '*') {
            fg = 0;
            tmp[i] = '#';
            ans += query(i + 1);
            tmp[i] = '?';
            ans += query(i + 1);
            tmp[i] = s[i];
        }
    }
    if (fg) {
        return mp[tmp];
    }
    return ans;
}

int main() {
    // #undef bug
    #ifdef bug
    freopen("plate.in", "r", stdin);
    freopen("plate.out", "w", stdout);
    #endif
    Q = read<int>();
    int op;
    while (Q --) {
        cin >> op >> s;
        tmp = s;
        if (op == 1) {
            ll ans = 1;
            for (char c : s) {
                if (c == '*') {
                    ans *= 36;
                }
                if (c == '?') {
                    ans *= 26;
                }
                if (c == '#') {
                    ans *= 10;
                }
            }
            ans -= query(0);
            cout << ans << '\n';
        } else {
            dfs(0);
        }
    }
    return 0;
}

4176: 树树计树 (tree)

题目内容

小 \(A\) 种了一棵有根树, 这棵树由 \(n\) 个节点 \(n−1\) 条边构成,节点编号为 \(1−n\), 其中 \(1\) 号节点是根节点。

现在小 \(A\) 想要给每个节点一个权值, 共有一以下两个要求:

  • 所有节点的权值都是 \(0\) 到 \(m\) 之间的一个整数。
  • 所有具有父子关系的两个节点, 记父节点权值为 \(x\), 子节点权值为 \(y\), 则有 x&y=y, 其中 & 为位运算与。

求有多少种不同的赋值方案,输出答案对 \(998244353\) 取模的结果。

输入格式

第一行两个正整数 \(n,m\).

接下来 \(n−1\) 行每行两个正整数 \(u_i​,v_i\)​ 描述树上的一条边。

输出格式

输出一行一个整数,表示答案对 \(998244353\) 取模的结果。


树形 DP + 数位 DP

会发现,每一位都有很强的独立性,在树上的分布也具有独立性,分布范围可以看作是一个联通块,利用树形 DP 来求得经过根节点的联通块个数,再利用数位 DP 来求得有 \(i\)\(1\) 时可以凑成的合法数字的个数。

// The code was written by yifan, and yifan is neutral!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))

template<typename T>
inline T read() {
    T x = 0;
    bool fg = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        fg |= (ch == '-');
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return fg ? ~x + 1 : x;
}

const int N = 3e5 + 5;
const int mod = 998244353;

int n;
ll m;
int dig[65];
ll dp[65][65][2], siz[N];
vector<int> e[N];

void dfs(int u, int fat) {
    siz[u] = 1;
    for (int v : e[u]) {
        if (v == fat)   continue ;
        dfs(v, u);
        siz[u] = siz[u] * (siz[v] + 1) % mod; // todo siz[v] 是包括了当前节点的联通块总个数,1是只有当前节点自己的联通块个数(只有这一个节点)
    }
}

ll qpow(ll x, ll y) {
    ll ans = 1;
    while (y) {
        if (y & 1) {
            ans = ans * x % mod;
        }
        y >>= 1;
        x = x * x % mod;
    }
    return ans % mod;
}

int main() {
    // #undef bug
    #ifdef bug
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    #endif
    n = read<int>(), m = read<ll>();
    int u, v;
    rep (i, 1, n - 1, 1) {
        u = read<int>(), v = read<int>();
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    rep (i, 0, 60, 1) {
        dig[i] = ((m >> i) & 1); // todo 将 m 转化成二进制,看看哪些位上是 1
    }
    dp[60][0][1] = 1; // todo 二进制第 60 位前面全是 0,和 m 相等的方案数
    dfs(1, 0); // todo 预处理出包括根节点的不同联通块的个数
    per (i, 60, 1, 1) { // todo 枚举最高位
        rep (j, 0, 60, 1) { // todo 枚举 1 的个数
            if (dp[i][j][0]) { // todo 小于 m 的情况
                dp[i - 1][j + 1][0] = (dp[i - 1][j + 1][0] + dp[i][j][0]) % mod;
                dp[i - 1][j][0] = (dp[i - 1][j][0] + dp[i][j][0]) % mod;
            }
            if (dp[i][j][1]) { // todo 等于 m 的情况
                if (dig[i - 1] == 0) { // todo 下一位不能填 1 时
                    dp[i - 1][j][1] = (dp[i - 1][j][1] + dp[i][j][1]) % mod;
                } else { // todo 下一位可以填 1 时
                    dp[i - 1][j][0] = (dp[i - 1][j][0] + dp[i][j][1]) % mod;
                    dp[i - 1][j + 1][1] = (dp[i - 1][j + 1][1] + dp[i][j][1]) % mod;
                }
            }
        }
    }
    ll ans = 0;
    rep (i, 0, 60, 1) {
        ll tmp = (dp[0][i][0] + dp[0][i][1]) % mod; // todo 用 i 个 1 能凑出合法数字的总的方案数
        ans = (ans + tmp * qpow(siz[1], i) % mod) % mod; // todo 每一位上都是独立的,有 i 位,共有 tmp 种情况
    }
    cout << ans % mod << '\n';
    return 0;
}

4174: 大鱼吃小鱼 (fish)

题目内容

在一个 \(n\) 行 \(m\) 列网格化管理的鱼塘里共有 \(n \times m\) 条鱼,每个格子里正好有一条鱼,其中第 \(i\) 行第 \(j\) 列的鱼的重量为 \(a_{i,j}\)​.

现在你将会控制其中的一只鱼,并尽可能吃掉其他的鱼让自己变得尽可能重。具体的说,你控制的鱼可以向四连通方向移动,记你的鱼重量为 \(x\)。 如果移动到了一个更大的鱼的领地那么就会被吃掉,如果移动到更小或者大小相等的鱼(记这条鱼的重量为 \(y\) )的领地那么就可以吃掉它,并更新你控制的鱼重量 \(x \leftarrow x+y\).

你还没有确定控制的是哪一条鱼,所以你需要对所有鱼回答: 如果最开始控制的是这条鱼,那么这条鱼最后最大的重量是多少。

输入格式

第一行两个正整数 \(n,m\).

接下来 \(n\) 行每行 \(m\) 个正整数 \(a_{i,j}\)​ 表示每条鱼的初始重量。

输出格式

输出 \(n\) 行每行 \(m\) 个整数,表示控制这条鱼最后最大的重量。


利用并查集的合并来合并总和信息,再通过 dfs 来更新。

// The code was written by yifan, and yifan is neutral!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))

template<typename T>
inline T read() {
    T x = 0;
    bool fg = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        fg |= (ch == '-');
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return fg ? ~x + 1 : x;
}

const int N = 1010;
const int M = 1e6 + 5;

int n, m;
int idn[M];
ll val[M];
vector<int> e[M];

struct union_find_set {
    int fa[M];
    ll sum[M];

    int Find(int x) {
        return fa[x] == x ? fa[x] : fa[x] = Find(fa[x]);
    }

    void Merge(int x, int y) {
        if (val[x] < val[y])    return ;
        if ((x = Find(x)) == (y = Find(y))) return ;
        e[x].emplace_back(y);
        fa[y] = x;
        sum[x] += sum[y];
    }

    int &operator[] (const int &x) {
        return fa[x];
    }
} ufs;

int check(int i) {
    return (i >= 1 && i <= n * m);
}

void dfs(int u, ll maxx, ll sum) {
    if (ufs.sum[u] >= maxx) {
        ufs.sum[u] = sum;
    }
    for (int v : e[u]) {
        dfs(v, val[u], ufs.sum[u]);
    }
}

int main() {
    // #undef bug
    #ifdef bug
    freopen("fish.in", "r", stdin);
    freopen("fish.out", "w", stdout);
    #endif
    n = read<int>(), m = read<int>();
    rep (i, 1, n * m, 1) {
        val[i] = read<ll>();
        ufs.sum[i] = val[i];
        ufs[i] = i;
        idn[i] = i;
    }
    sort(idn + 1, idn + n * m + 1, [](int x, int y) {
        return val[x] < val[y];
    });
    int tmp;
    rep (i, 1, n * m, 1) {
        if (check(tmp = idn[i] + m)) {
            ufs.Merge(idn[i], tmp);
        }
        if (check(tmp = idn[i] - m)) {
            ufs.Merge(idn[i], tmp);
        }
        if (idn[i] % m != 0 && check(tmp = idn[i] + 1)) {
            ufs.Merge(idn[i], tmp);
        }
        if (idn[i] % m != 1 && check(tmp = idn[i] - 1)) {
            ufs.Merge(idn[i], tmp);
        }
    }
    rep (i, 1, n * m, 1) {
        if (ufs[i] == i) {
            dfs(i, 2e18, 0);
        }
    }
    rep (i, 1, n * m, 1) {
        cout << ufs.sum[i] << ' ';
        if (i % m == 0) {
            putchar('\n');
        }
    }
    return 0;
}

4175: 图鉴收集 II (handbook)

题目内容

下北泽王国以真夏夜的银梦和孤独摇滚两部作品闻名世界,吸引了许多北泽狂热者前往圣地巡礼。王国的 \(n\) 个城市里散布着 \(k\) 种不同的北泽图鉴, 编号为 \(1,2,\dots,k\),其中在第 \(i(i=1,2,\dots,n)\) 个城市里能够发现第 \(a_i\)​ 种图鉴,保证每种图鉴至少在其中一个城市出现。

\(2919\) 年 \(8\) 月 \(10\) 日, 为了避免游客数量过多造成王国内部的混乱,在这个特殊的日子里下北泽实行严格的交通管制,整个王国只开放 \(n−1\) 条城市与城市之间的双向道路,使得整个王国在连通的情况下只保证最低的连通性。第 \(i\) 条双向道路连通城市 \(u_i\)​ 和 \(v_i\)​, 通行时间为 \(w_i\)​.

现在有 \(q\) 个来到下北泽王国收集图鉴的北泽狂热者,第 \(i\) 位会从 \(s_i\)​ 城市的空港入境,从 \(t_i\)​ 城市的空港出境,你需要帮助他设计一条从 \(s_i\)​ 到 \(t_i\)​ 的路径收集北泽图鉴,使得能够获得所有不同种的图鉴且通行时间最短。

输入格式

第一行三个正整数 \(n,k,q\), 分别表示城市个数, 图鉴类型数量以及收集图鉴的狂热者数量。

接下来一行 \(n\) 个正整数 \(a_1​,a_2​,\dots,a_n\)​, 表示每个城市可以收集到的图鉴种类。

接下来 \(n−1\) 行每行三个正整数 \(u_i​,v_i​,w_i\)​,描述一条可以通行的双向道路,保证 \(n\) 个城市两两可达。

接下来 \(q\) 行每行两个正整数 \(s_i​,t_i\)​ 表示每个北泽狂热者图鉴收集的路径起点和终点。

输出格式

输出 \(q\) 个整数表示每个北泽狂热者收集所有图鉴所需要的最短时间。


只有 \(30\) 分,跑的分层图最短路。

// The code was written by yifan, and yifan is neutral!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))

template<typename T>
inline T read() {
    T x = 0;
    bool fg = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        fg |= (ch == '-');
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return fg ? ~x + 1 : x;
}

const int N = 1e4 + 5;

using til = tuple<int, ll>;
using tli = tuple<ll, int>;

int n, k, q, cnt;
int a[N], idn[(1 << 6) + 5][N];
ll dis[(N * (1 << 6)) + 5];
bool vis[(N * (1 << 6)) + 2];
vector<til> E[N], e[N];

void dij(int s) {
    priority_queue<tli, vector<tli>, greater<tli> > q;
    rep (i, 1, cnt, 1) {
        dis[i] = 1e18;
        vis[i] = 0;
    }
    q.emplace(0, s);
    dis[s] = 0;
    int u;
    ll w;
    while (!q.empty()) {
        tie(w, u) = q.top();
        q.pop();
        if (vis[u]) continue ;
        vis[u] = 1;
        for (til it : e[u]) {
            int v;
            tie(v, w) = it;
            if (dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                q.emplace(dis[v], v);
            }
        }
    }
}

int main() {
    // #undef bug
    #ifdef bug
    freopen("handbook.in", "r", stdin);
    freopen("handbook.out", "w", stdout);
    #endif
    n = read<int>(), k = read<int>(), q = read<int>();
    rep (i, 1, n, 1) {
        a[i] = read<int>();
    }
    int x, y;
    ll w;
    rep (i, 1, n - 1, 1) {
        x = read<int>(), y = read<int>(), w = read<ll>();
        E[x].emplace_back(y, w);
        E[y].emplace_back(x, w);
    }
    rep (i, 0, (1 << k) - 1, 1) {
        rep (j, 1, n, 1) {
            idn[i][j] = ++ cnt;
        }
    }
    rep (i, 0, (1 << k) - 1, 1) { // j 的状态
        rep (j, 1, n, 1) {
            if (!(i & (1 << (a[j] - 1))))   continue ;
            for (til it : E[j]) {
                tie(x, y) = it; // x 起点
                rep (h, 0, (1 << k) - 1, 1) { // h x 的状态
                    if ((h | (1 << (a[j] - 1))) != i)   continue ;
                    e[idn[h][x]].emplace_back(idn[i][j], y);
                }
            }
        }
    }
    int s, t;
    rep (i, 1, q, 1) {
        s = read<int>(), t = read<int>();
        dij(idn[(1 << (a[s] - 1))][s]);
        cout << dis[idn[(1 << k) - 1][t]] << '\n';
    }
    return 0;
}
posted @ 2023-09-24 20:53  yi_fan0305  阅读(115)  评论(0编辑  收藏  举报