Loading [MathJax]/jax/element/mml/optable/GeneralPunctuation.js

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

4173: 车牌 (plate)#

题目内容#

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

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

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

输入格式#

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

接下来 Q 行形如 "1S""2S" 分别表示第一种操作和第二种操作, 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 个节点 n1 条边构成,节点编号为 1n, 其中 1 号节点是根节点。

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

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

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

输入格式#

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

接下来 n1 行每行两个正整数 u_i​,v_i​ 描述树上的一条边。

输出格式#

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


树形 DP + 数位 DP

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

// 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;
}

作者:yifan0305

出处:https://www.cnblogs.com/yifan0305/p/17726643.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

转载时还请标明出处哟!

posted @   yi_fan0305  阅读(149)  评论(0编辑  收藏  举报
努力加载评论中...
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示