「解题报告」2023-09-24 CSP-S 公开模拟赛
4173: 车牌 (plate)#
题目内容#
小 Y 毕业之后来到了车管所工作,他现在掌管着下北泽全市的车牌分配。具体的说,下北泽的车牌是一个长度为 5 的字符串,字符串的每个字符是一个 0−9 的数字或者一个 A−Z 的大写字母。为了避免混淆,每种字符串的车牌最多只能被分配一次,初始没有任何车牌被分配。现在你需要支持以下两种操作:
-
给定一个长度为 5 的车牌模板, 每个字符可以是 0−9 的数字, 一个 A−Z 的大写字母表示车牌的这个位置必须是该字符, 也可以是三种通配符: 问号
?
表示可以车牌的这个位置可以是任何字母; 井号#
表示车牌的这个位置可以是任何数码; 星号*
表示车牌的这个位置可以是任何字符。你需要查询满足该字符模板的未分配车牌个数。 -
给定一个长度为 5 的车牌, 将这个车牌设置为已分配。保证之前该车牌没有被分配过。
输入格式#
第一行一个正整数 Q 表示总操作次数。
接下来 Q 行形如 "1S" 或 "2S" 分别表示第一种操作和第二种操作, S 对应为车牌模板字符串或者车牌字符串。
输出格式#
对于每个第一种操作,输出一行一个正整数表示答案。
原本写了个 trie 树,只有 65 分,满分做法是借鉴的 Ciaxin 的做法。(%%% Ciaxin)再插入时,对每个数字都替换成 #
,对每个字母都替换成 ?
,然后再将此字符串插入其中,可以使用 map
或 unordered_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;
}
作者:yifan0305
出处:https://www.cnblogs.com/yifan0305/p/17726643.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
转载时还请标明出处哟!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步