251013

news/2025/10/14 2:29:31/文章来源:https://www.cnblogs.com/Young-Cloud/p/19139848

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.mzph.cn/news/936559.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简谈误差与不确定度

唉学物理的怎么都这么坏弄得公式都没搞明白就拿来用了(晚自习闲着没事写的,内容比较 Trivial,大家图一乐就行。 我们主要谈论其中的一些数学直觉上的理解。 1. 随机误差统计规律 由统计规律可知,概率密度函数 \(f(…

可怕!我的Nodejs系统因为日志打印了Error 对象就崩溃了 Node.js System Crashed Because of Logging an Error

@目录报错信息报错截图错误分析场景复现小秘密大揭秘!🔍console.log虽好,但请勿用它来记录PROD错误!日志库的"小烦恼"什么是循环引用?🌀怎样才能让我们的日志系统乖乖听话呢?✨1. 只记录我们需要的…

实践

ans多取合法方案的max/min结果肯定不劣。对于操作“change x y:把a[x]修改为y”,无论是提前continue掉还是循环末尾,一定要记得令a[x]=y!!!模数MOD特殊一定要注意!遇见小模数MOD,可能复杂度与MOD相关。 有可能…

数据结构字符串和图

1.字符串的存储 1.1.字符数组和STLstring char s[N]strlen(s+i):\(O(n)\)。返回从 s[0+i] 开始直到 \0 的字符数。 strcmp(s1,s2):\(O(\min(n_1,n_2))\)。若 s1 字典序更小返回负值,两者一样返回 0,s1 字典序更大返…

字典dict

2025.10.14 1.字典的键值必须是不可变的,也就是说元祖,形如下面的初始化是可以的dict1 = {(1, 2): 1} dict1 = {a: 1} dict1 = {}

结婚证识别技术:融合计算机视觉、深度学习与自然语言处理的综合性AI能力的体现

在数字化浪潮席卷各行各业的今天,如何高效、准确地处理海量纸质证件信息,成为提升政务服务与金融业务效率的关键。结婚证作为证明婚姻关系的核心法律文件,因而,结婚证识别技术应运而生。它不仅是光学字符识别技术的…

上下文丢失

2025.10.14 位置编码外推失效是Transformer模型在长文本推理中出现上下文丢失的最常见架构限制,因为训练时使用的固定位置编码(如正弦编码)无法有效外推至超出训练长度的序列位置,导致位置信息丢失。 残差连接梯度…

数据结构序列

不要从数据结构维护信息的角度来思考问题,而是从问题本身思考需要哪些信息,数据结构只是维护信息的工具!!! 可减信息,如区间和、区间异或和 直接用前缀和实现,复杂度 O(n)+O(1)+O(n)。 可重复贡献信息,如区间最…

上下文学习(In-context Learning, ICL)

2025.10.14 上下文学习(In-context Learning, ICL)的核心机制是在推理阶段不更新模型参数,利用提示中的少量示例引导模型生成适应新任务的输出。也就是在不更新参数的情况下,利用提示中的示例让模型在内部条件化地…

混淆矩阵

2025.10.14 混淆矩阵可以显示模型的所有预测结果,包括真正例、假正例、真负例和假负例,从而帮助分析模型的性能 混淆矩阵不仅仅显示准确率,还提供更详细的分类结果 混淆矩阵与训练损失无关 混淆矩阵不涉及超参数设置…

提示词工程实践指南:从调参到对话的范式转变

写在前面 作为一名长期与代码打交道的工程师,我们习惯了编译器的严格和确定性——相同的输入永远产生相同的输出。但当我们开始使用生成式AI时,会发现这是一个完全不同的世界。最近在系统学习Google的AI课程时,我整理…

泛化能力

2025.10.14 在大型语言模型的工程实践中,提高泛化能力的最常见策略是使用更大的预训练数据集,因为更多数据可以帮助模型学习更泛化的表示,例如GPT-3和BERT等模型都强调大规模数据集的应用。

JVM引入

虚拟机与 JVM 虚拟机(Virtual Machine),就是一台虚拟的计算机。它是一款软件,用来执行一系列的虚拟计算机指令。 虚拟机可以分为系统虚拟机和程序虚拟机:Visual Box、VMware 就属于系统虚拟机,它们完全是对物理计…

shiro 架构

一、subject(当前用户信息) 二、SecurityManager(所有用户管理) 三、Realm(数据连接)

[音视频][HLS] HLS_downloader

[音视频][HLS] HLS_downloader$(".postTitle2").removeClass("postTitle2").addClass("singleposttitle");01 简介 1.1 功能: 一个简单的HLS下载器,使用go语言实现 1.2 执行方式 如果…

Python-weakref技术指南

Python weakref 模块是 Python 标准库中用于处理对象弱引用的重要工具。它允许程序员创建对对象的弱引用,这种引用不会增加对象的引用计数,从而不影响对象的垃圾回收过程。本报告将全面介绍 weakref 模块的概念、工作…

从众多知识汲取一星半点也能受益匪浅【day11(2025.10.13)】

Enjoy 基于代码思考问题 先理清楚代码是否用上了文档所定义的api

王爽《汇编语言》第四章 笔记

4.2 源程序 4.2.1 伪指令在汇编语言的源程序中包含两种指令:汇编指令、伪指令。 (1)汇编指令:有对应机器码的指令,可以被编译为机器指令,最终被CPU所执行。 (2)伪指令:没有对应的机器指令,最终不被CPU所执行…

10.13总结

import java.util.*; import java.util.concurrent.TimeUnit; public class ArithmeticPractice { private Set generatedQuestions = new HashSet<>(); private List questions = new ArrayList<>(); pri…