【CCPC】The 2024 Shanghai Collegiate Programming Contest F

devtools/2024/10/18 18:50:13/

羁绊大师

#动态规划 #图论 #并查集 #背包

题目描述

在某一自走棋游戏中,胖胖龙此刻拥有 n n n 个英雄,其中第 i i i 个英雄拥有两种羁绊,分别为 a i , b i ( a i < b i ) a_i, b_i(a_i < b_i) ai,bi(ai<bi)
不存在两个英雄拥完全相同的两种羁绊,即对于 1 ≤ i < j ≤ n 1 ≤ i < j ≤ n 1i<jn, 有 a i ≠ a j a_i\neq a_j ai=aj b i ≠ b j b_i \neq b_j bi=bj 。因为该游戏模式
的特殊性,一共具有 m 种羁绊,且每种羁绊至至至多多多只有两个英雄拥有。当胖胖龙处于等级 L L L 时,可以选
择自己的 L L L 个英雄上阵。对于每种羁绊,当且仅当上阵英雄中有两个英雄拥有此羁绊时,此羁绊为激活
状态。
胖胖龙的梦想是成为羁绊大师,他希望选出 L L L 个英雄上阵,使得自己激活的羁绊种数尽可能多。胖胖龙
希望你能帮帮他,如果你帮助了他,他会给你跳一段节奏鲜明而富有动感的舞蹈。
请你分别对 L ∈ { 1 , 2 ⋅ ⋅ ⋅ n } L ∈ \{1, 2 · · · n\} L{1,2⋅⋅⋅n} n n n种情况,分别输出激活羁绊的最大数量。

输入格式

第一行,包含两个非负整数 n, m(1 ≤ n ≤ 105, n ≤ m ≤ 2n) 分别表示胖胖龙此时拥有的英雄数量,以及
游戏中存在羁绊的数量。
接下来 n 行,第 i 行包含两个整数 ai, bi(1 ≤ ai < bi ≤ m) 分别表示第 i 个英雄拥有的两种羁绊。

输出格式

输出一行,包含 n 个整数,从左到右第 i 个整数表示当 L = i 时,胖胖龙最多能够激活的羁绊种数。整
数之间用一个用空格隔开。

样例 #1

样例输入 #1

10 10
1 2
2 3
1 3
4 5
5 6
6 7
4 7
8 9
9 10
8 10

样例输出 #1

0 1 3 4 4 6 7 7 8 10

解法

解题思路

对于一个英雄有两个羁绊,一个羁绊最多有两个英雄拥有。如果我们把羁绊看做一个点,英雄看做一条边,那么整张图只有环和链,不存在环中带链,因为每个点最多就两个度数。

那么,题目要求我们选择最多的羁绊,而这个羁绊必须被两个英雄共有才生效,转换在图中就是,环对应的贡献就是环的大小,链对应的贡献就是链的大小 − 1 -1 1,因此我们要尽可能选多的环。

而对于环和链的维护,一种是建图后染色,一种是使用并查集,这里使用并查集来维护。

那么,我们可以把环看做物品, L L L看做背包容量,然后去跑背包。如果背包容量大于全部环
的大小,那么我们只需要判断能取多少个链即可,需要贪心地从大到小来取,因为要尽可能少的取。

而对于背包容量小于环的,如果跑完背包后刚好装满,那么贡献就是背包容量,否则就是背包容量 − 1 -1 1,因为我们可以把剩下的环拆成链,这样只有一条链,负面贡献为 1 1 1

由于这个物品实际上是会重复的,并且至多只会有 m \sqrt m m 种,如果单纯跑 01 01 01背包复杂度将会在 O ( n m ) O(nm) O(nm)

因此我们,需要统计种类,然后使用二进制优化,或者单调队列优化来优化这个多重背包,使用单调队列优化复杂度在 O ( m ∗ n ) O(\sqrt m *n) O(m n)。当然二进制优化也能过 ,并且这题种类不会很多,使用二进制优化跑的更快。

代码(二进制优化)

//solve函数
const int N = 2e5 + 10;int fa[N], siz[N];
inline int find(int x) {return fa[x] = fa[x] == x ? x : find(fa[x]);
}void solve() {int n, m;std::cin >> n >> m;for (int i = 1; i <= m; i++) {siz[i] = 1;fa[i] = i;}std::vector<bool> cyc(m);for (int i = 1; i <= n; i++) {int u, v;std::cin >> u >> v;int a = find(u), b = find(v);if (a == b) {cyc[a] = true;}else {fa[b] = a;siz[a] += siz[b];}}int sum = 0;std::map<pii, int>mp;std::vector<int>b;for (int i = 1; i <= m; ++i) {if (fa[i] == i) {if (cyc[i]) {mp[{siz[i], siz[i]}]++;sum += siz[i];}else {b.push_back(siz[i] - 1);}}}std::vector<int>f(n + 1);std::vector<int>aa, bb;for (auto& [t, z] : mp) {auto [y, x] = t;for (int k = 0; k < 32; ++k) {if (z - (1LL << k) <= 0) break;aa.push_back((1LL << k) * y);bb.push_back((1LL << k) * x);z -= 1LL << k;}if (z) {aa.push_back(z * y);bb.push_back(z * x);}}for (int i = 0; i < bb.size(); ++i) {for (int j = n; j >= aa[i]; --j) {f[j] = std::max(f[j], f[j - aa[i]] + bb[i]);}}std::sort(b.begin(), b.end(), std::greater<>());int j = 0;std::vector<int>res(n + 1);int s = sum;for (int i = 1; i <= n; ++i) {if (i <= s) {res[i] = i - !(f[i] == i);}else {while (i > sum) sum += b[j++];res[i] = i - j;}}for (int i = 1; i <= n; ++i) {std::cout << res[i] << " ";}std::cout << "\n";
}signed main() {std::ios::sync_with_stdio(0);std::cin.tie(0);std::cout.tie(0);int t = 1;//std::cin >> t;while (t--) {solve();}
}

代码(单调队列优化)

const int N = 2e5 + 10;int fa[N], siz[N];
int q[N];
inline int find(int x) {return fa[x] = fa[x] == x ? x : find(fa[x]);
}void solve() {int n, m;std::cin >> n >> m;for (int i = 1; i <= m; i++) {siz[i] = 1;fa[i] = i;}std::vector<bool> cyc(m);for (int i = 1; i <= n; i++){int u, v;std::cin >> u >> v;int a = find(u), b = find(v);if (a == b) {cyc[a] = true;}else {fa[b] = a;siz[a] += siz[b];}}int sum = 0;std::map<pii, int>mp;std::vector<int>b;for (int i = 1; i <= m ; ++i) {if (fa[i] == i) {if (cyc[i]) {mp[{siz[i], siz[i]}]++;sum += siz[i];}else {b.push_back(siz[i] - 1);}}}std::vector<int>f(n + 1);for (auto& [x, s] : mp) {auto g = f;auto [v, w] = x;for (int j = 0; j < v; ++j) {int h = 0, t = -1;for (int k = j; k <= n; k += v) {while (h <= t && q[h] < k - s * v) {h++;}if (h <= t) {f[k] = std::max(g[k], g[q[h]] + (k - q[h]) / v * w);}while (h <= t && g[k] >= g[q[t]] + (k - q[t]) / v * w) {t--;}q[++t] = k;}}}std::sort(b.begin(), b.end(), std::greater<>());int j = 0;std::vector<int>res(n + 1);int s = sum;for (int i = 1; i <= n; ++i) {if (i <= s) {res[i] = i - !(f[i]==i);}else {while (i > sum) sum += b[j++];res[i] = i - j;}}for (int i = 1; i <= n; ++i) {std::cout << res[i] << " ";}std::cout << "\n";
}signed main() {std::ios::sync_with_stdio(0);std::cin.tie(0);std::cout.tie(0);int t = 1;//std::cin >> t;while (t--) {solve();}
};

http://www.ppmy.cn/devtools/121943.html

相关文章

使用rust写一个Web服务器——多线程版本

文章目录 模拟慢请求多线程Web服务器实现为每个请求单独生成一个线程限制创建线程的数量ThreadPool的初始化ThreadPool的存储ThreadPool的设计 关闭和资源清理为ThreadPool实现Drop停止工作线程测试 仓库地址&#xff1a; 1037827920/web-server: 使用rust编写的简单web服务器 …

拓扑排序简介

拓扑排序(Topological Sort)是一种重要的图算法,用于对有向无环图(DAG, Directed Acyclic Graph)中的节点进行排序。拓扑排序的结果是一种线性序列,使得对于图中的任意一条有向边(u, v),顶点u都在顶点v之前。这种排序常用于任务调度、编译器依赖关系分析等领域。 拓…

程序计数器(学习笔记)

程序计数器是一块较小的内存空间&#xff0c;它的作用可以看做是当前线程所执行的字节码的信号指示器&#xff08;偏移地址&#xff09;&#xff0c;Java编译过程中产生的字节码有点类似编译原理的指令&#xff0c;程序计数器的内存空间存储的是当前执行的字节码的偏移地址 因为…

linux网络编程实战

前言 之前找工作的之后写了一些网络编程的笔记和代码&#xff0c;然后现在放到csdn上保存一下。有几个版本的&#xff0c;看看就好。就是简单的实现一下服务端和客户端之间的交互的&#xff0c;还没有我之前上linux编程课写的代码复杂。 哦对了&#xff0c;这个网络编程的代码对…

Debezium日常分享系列之:Debezium 3.0.0.Final发布

Debezium日常分享系列之&#xff1a;Debezium 3.0.0.Final发布 Debezium 核心的变化需要 Java 17基于Kafka 3.8 构建废弃的增量信号字段的删除每个表的详细指标 MariaDB连接器的更改版本 11.4.3 支持 MongoDB连接器的更改MongoDB sink connector MySQL连接器的改变MySQL 9MySQL…

软考系统分析师知识点二:经济管理

前言 今年报考了11月份的软考高级&#xff1a;系统分析师。 考试时间为&#xff1a;11月9日。 倒计时&#xff1a;35天。 目标&#xff1a;优先应试&#xff0c;其次学习&#xff0c;再次实践。 复习计划第一阶段&#xff1a;扫平基础知识点&#xff0c;仅抽取有用信息&am…

企业必备:搭建大模型应用平台实操教程

最近AI智能体很火&#xff0c;AI智能体平台化产品肯定属于大公司的。但在一些场景下&#xff0c;尤其是对业务数据要求很高的公司&#xff0c;那就只能用私有大模型。不一定完全是为了对外提供服务&#xff0c;对内改造工作流也是需要的。所以 我感觉未来大部分企业都会搞一个…

结合大语言模型的机械臂抓取操作简单介绍

一、大语言模型与机械臂抓取的基本操作 1. 大语言模型简介 大语言模型是基于深度学习技术构建的自然语言处理模型&#xff0c;能够生成、理解和处理文本信息。这些模型通过训练大量的文本数据&#xff0c;学习语法、上下文和常识&#xff0c;能够执行多种任务&#xff0c;如文…