当前位置: 首页 > news >正文

251013

2023 ICPC Macau

ICPC Macau

感觉是一套非常困难的题

A

可以发现选择一个 +1 与去除一个 -1 对行列的效果是一样的,所以我们可以先把所有的 -1 选上。之后改变某个数的选择状态都是对对应的行列和加一。接下来就可以贪心了:一行一行考虑,优先满足缺 +1 多的列:

void solve() {int n = 0;std::cin >> n;std::vector g(n, std::string());for (auto &s : g) {std::cin >> s;}std::vector row(n, 0);std::vector col(n, std::pair<int, int>());for (auto &i : row) {std::cin >> i;}for (int i = 0; i < n; ++i) {std::cin >> col[i].first;col[i].second = i;}std::vector ans(n, std::vector(n, 0));for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {if (g[i][j] == '-') {row[i] += 1;col[j].first += 1;ans[i][j] = 1;}}}for (int i = 0; i < n; ++i) {std::sort(col.begin(), col.end(), std::greater<std::pair<int, int>>());for (int j = 0; j < n; ++j) {if (row[i] <= 0 || col[j].first <= 0) {break;}row[i] -= 1;col[j].first -= 1;ans[i][col[j].second] ^= 1;}}bool ok = true;for (int i = 0; i < n; ++i) {if (row[i] != 0 || col[i].first != 0) {std::cout << "NO\n";return;}}std::cout << "YES\n";for (auto &v : ans) {for (auto &i : v) {std::cout << i;}std::cout << '\n';}return;
}

D

首先需要观察出符合条件的连通块的类型并不多(其实数量也不多)。注意题目给出的条件:最大度不超过 3。这个条件怎么用呢?考虑一个有 \(x\) 个点且满足条件的连通块,这个连通块至少需要 \(2(x - 1)\) 条边,即单独看红边或蓝边都会形成一颗树,而在最大度不超过 3 的条件下,一个有 \(x\) 个点的连通块最多有 \(3x \over 2\),解 \(2(x - 1) \leq {2x \over 2}\)\(x \leq 4\)

接下来就是找符合条件的连通块了。

  • 一个点天然符合条件
  • 两个点符合条件当且仅当两个点之间同时存在两种颜色的边
  • 三个点符合条件,则必然有两个点之间同时存在两种颜色的边,然后找一下这两个点是否同时连一个相同的点(注意处理边的颜色)
  • 四个点符合条件,此时 4 个点的度数都是 3,而且同色边将这四个点连成一条长度为 3 的链。于是我们对一个点,尝试找以它开始的用红色边连起来的长度为 3 的链,若找得到,则记录这四个点,再看蓝色边能不能将这 4 个点连通起来。

后 3 种情况都会将同一个连通块统计两次,所以我们干脆故意将一个点形成的连通块也统计两次,最后求和除 2 就好了:

std::vector<int> adj[2][N + 5];
// i64 ans;int deg(int idx) {return adj[0][idx].size() + adj[1][idx].size();
}int find(int u, int v) {for (auto &to : adj[0][u]) {if (to != v) {return to;}}return 0;
}int node[4];bool vis[N + 5];
// 检查所枚举的连通块通过蓝边是否连通
bool chk(int cnt) {for (int i = 0; i < cnt; ++i) {vis[node[i]] = false;}std::queue<int> q;q.push(node[0]);vis[node[0]] = true;while (not q.empty()) {int cur = q.front();q.pop();for (auto &to : adj[1][cur]) {if (vis[to]) {continue;}for (int i = 0; i < cnt; ++i) {if (to == node[i]) {q.push(to);vis[to] = true;break;}}}}for (int i = 0; i < cnt; ++i) {if (not vis[node[i]]) {return false;}}return true;
}void solve() {int n = 0, m = 0;std::cin >> n >> m;for (int i = 0; i < m; ++i) {int u = 0, v = 0, w = 0;std::cin >> u >> v >> w;adj[w][u].push_back(v);adj[w][v].push_back(u);}std::array<int, 5> cnt{};for (int i = 1; i <= n; ++i) {// 一个点cnt[1] += 2;int d = deg(i);if (d <= 1) {continue;}// 两个点int j = 0;bool ok = false;for (int &u : adj[0][i]) {for (int &v : adj[1][i]) {if (u == v) {j = u;ok = true;}}}if (ok) {cnt[2] += 1;}// 三个点if (deg(j) == 3 && d == 3) {// 找第 3 个点int k = 0;int ic = 0;if (adj[ic][i].size() == 1) {ic = 1;}if (adj[ic][i][0] == j) {k = adj[ic][i][1];}else {k = adj[ic][i][0];}bool ok = false;for (auto &u : adj[ic ^ 1][j]) {if (u == k) {ok = true;break;}}if (ok) {cnt[3] += 1;}}// 四个点if (adj[0][i].size() == 1) {node[0] = i;node[1] = adj[0][i][0];node[2] = find(node[1], node[0]);node[3] = find(node[2], node[1]);if (chk(4)) {cnt[4] += 1;}}}std::cout << (cnt[1] + cnt[2] + cnt[3] + cnt[4]) / 2 << '\n';return;
}

H

树形 dp 及其优化

首先要分析合法序列的性质:

一个包含 \(1\)\(n\) 每个数各一次的序列一定是合法的,在这个显然的合法的序列的基础上,考虑如何操作才能让这个序列仍然维持合法或破坏合法性。手玩一下能总结出合法序列满足:对于每个子树,序列中包含的这个子树中的点的个数(可重复)一定要大于等于这个子树的大小。而且序列的合法性与序列顺序无关

于是设计 \(f_{u, i}\) 当有 \(i\) 个点都属于 \(u\) 子树中的点时,这 \(i\) 个点的分法。

为了方便解释,我们定义 \(g_{u, j, i}\) 表示当有 \(i\) 个点都属于 \(u\) 的前 \(j\) 个儿子子树时,这 \(i\) 个点的分法,(这 i 个点不包含 \(u\)

先看朴素的转移:

\[g_{u, j, S} = \sum_{x + y = S \And siz_j \leq y} g_{u, j - 1, x} + f_{v_j, y} \]

\[f_{u, S} = \sum_{x + y = S} {g_{u, m, x} \over y!} \]

第一个转移是从 \(u\) 的儿子转移到 \(u\)\(siz_j \leq y\) 是为了满足序列合法性的必要条件。第二个转移是当儿子都转移完后,加入一些数 \(u\)。除以阶乘的原因是,当我们最后求答案时,我们想直接输出 \(n! \times f_{1, n}\),但是对于相同的数,我们需要除一个阶乘,这个阶乘我们在转移的时候就给他带上。

显然以上转移一次是 \(O(n^2)\) 的,不可接受。

但实际上,第一个转移中的 \(y\) 还有一个上界,即 \(y < siz_{v_j} + depth_{v_j}\),当大于这个值时位置就不够了。结合下届,我们发现每次只用 \(depth_{v_j} \times depth_{u}\) 次转移。由于树是随机的,这个差不多是 \(log_n^2\) 的量级。

然后就是实现了:


i64 fac[N + 5], ifac[N + 5]; // 阶乘和阶乘逆元
std::vector<int> adj[N + 5];
int siz[N + 5], dep[N + 5];
std::vector<i64> f[N + 5];
i64 g[N + 5], h[N + 5]; // 辅助转移i64 qpow(i64 a, i64 p) {i64 res = 1ll;while (p) {if (p & 1) {(res *= a) %= Mod;}(a *= a) %= Mod;p >>= 1;}return res;
}
i64 inv(i64 a) {return qpow(a, Mod - 2);
}
void add(i64 &a, i64 b) {a += b;if (a >= Mod) {a -= Mod;}
}void trans(int x, int y) {// 旧区间[l, r]// 经过此次转移后得到的新区间 [l + siz[y], r + siz[y]]int l = siz[x], r = siz[x] + dep[x];for (int i = l; i <= r; ++i) {h[i] = g[i];g[i] = 0ll;}for (int i = 1; i <= siz[y]; ++i) {g[r + i] = 0;}for (int i = l; i <= r; ++i) {for (int j = siz[y]; i + j <= r + siz[y] && j < siz[y] + dep[y]; ++j) {add(g[i + j], h[i] * f[y][j - siz[y]] % Mod);}}
}void solve() {int n = 0;std::cin >> n;dep[1] = 1;for (int i = 2; i <= n; ++i) {int f = 0;std::cin >> f;adj[f].push_back(i);dep[i] = dep[f] + 1;}for (int x = n; x >= 1; --x) {siz[x] = 0;for (int i = 0; i <= dep[x]; ++i) {g[i] = 0ll;}g[0] = 1;for (auto &y : adj[x]) {trans(x, y);siz[x] += siz[y];}// 加入等于 x 的数for (int i = siz[x] + dep[x]; i > siz[x]; --i) {for (int j = siz[x]; j < i; ++j) {add(g[i], g[j] * ifac[i - j] % Mod);}}siz[x] += 1;f[x].assign(dep[x], 0ll);for (int i = 0; i < dep[x]; ++i) {f[x][i] = g[i + siz[x]];}}std::cout << f[1][0] * fac[n] % Mod << '\n';
}
http://www.hskmm.com/?act=detail&tid=30471

相关文章:

  • 简谈误差与不确定度
  • 可怕!我的Nodejs系统因为日志打印了Error 对象就崩溃了 Node.js System Crashed Because of Logging an Error
  • 前言
  • 实践
  • 数据结构字符串和图
  • 字典dict
  • 结婚证识别技术:融合计算机视觉、深度学习与自然语言处理的综合性AI能力的体现
  • 上下文丢失
  • 数据结构序列
  • 上下文学习(In-context Learning, ICL)
  • 混淆矩阵
  • 提示词工程实践指南:从调参到对话的范式转变
  • Multi-Head Attention机制
  • 泛化能力
  • JVM引入
  • shiro 架构
  • test9 - post
  • 高级语言程序设计第一次作业
  • Python-weakref技术指南
  • 第二次
  • 从众多知识汲取一星半点也能受益匪浅【day11(2025.10.13)】
  • 王爽《汇编语言》第四章 笔记
  • 10.13总结
  • MySql安装中的问题
  • 题解:AT_agc050_b [AGC050B] Three Coins
  • go:generate 指令
  • 光栅化
  • 图形学中的变换
  • Unity URP 体积云
  • 使用DirectX绘制天空盒并实现破坏和放置方块