Good Bye 2024

news/2024/12/31 4:57:14/文章来源:https://www.cnblogs.com/Lanly/p/18638613
省流版
  • A. 考虑存在相邻两个数组成三角形即可
  • B. 仅考虑唯一取值的元素是否占满了当前元素的所有取值
  • C. 分阶段考虑贡献,每阶段长度减半,贡献是中点值*区间数量+总偏移量和,维护总偏移量
  • D. 最大值取于俩数组从小到大排序。对于操作,等价于修改有序数组的最右边的数,维护答案
  • E. 两种必胜情况,一种是q在叶子上,p不在叶子上,另一种是q的邻居能一步到叶子,考虑p的取值,统计非叶子非好点的数量即可

A - Tender Carpenter (cf2053 A)

题目大意

给定一个长度为 \(n\) 的数组,问是否存在多种切分方法,使得切分出的若干个连续子数组的每一个数组,任取三个数(可以是同一个),它们都能组成一个三角形。

解题思路

一个最朴素的切分方法就是每个元素为一组,这样显然是满足条件的。

考虑还有没有其他的切分方法。朴素的想法是考虑两个相邻的元素,如果两个较小值大于较大值,那么这两个元素可以组成一个三角形,那么这两个元素就可以切分到一组中。

这样就得到另一个切分方法,因此就存在多种切分方法了。否则就不存在了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;int main(void) {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t;cin >> t;while (t--) {int n;cin >> n;vector<int> a(n);for (auto& i : a)cin >> i;bool ok = false;for (int i = 0; i < n - 1; ++i) {int minn = min(a[i], a[i + 1]);int maxx = max(a[i], a[i + 1]);ok |= (minn + minn > maxx);}if (ok)cout << "YES" << '\n';elsecout << "NO" << '\n';}return 0;
}


B - Outstanding Impressionist (cf2053 B)

题目大意

有一 \(n\) 个元素的数组,但你只记得第 \(i\) 个元素的值的范围是 \([l_i,r_i]\)

现在对于每个元素,是否存在一种情况,它的值是独一无二的,即其他元素的值都不等于它。

解题思路

考虑当前元素 \(a_i\),它的范围是 \([l_i,r_i]\),如果它不是独一无二的,那么 \([l_i,r_i]\) 中的每个数在其他位置都一定出现了。

而其他位置的元素的范围是 \([l_j,r_j]\),如果 \(l_j == r_j\),那么该位置只能取一个值,否则它可以避免和 \(a_i\)取同样的值。

因此,我们只关注那些只能取一个值(\(l_j == r_j\))的元素,并记录一下\(l_j\)这个值出现了,记 \(forbid[l_j] = 1\)

然后对于 \(a_i\),如果 \([l_i,r_i]\) 中的每个数都在之前出现过(\(\sum_{j=l_i}^{r_i} forbid_j == r_i - l_i + 1\)),那么 \(a_i\) 就不是独一无二的。用前缀和即可\(O(1)\)判断上述条件。

注意要消除 \(a_i\) 本身的影响,即若 \(l_i == r_i\),那么要去除它对 \(forbid[l_i]\) 的贡献。

这里就需要记录两个数组:\(cnt\)\(forbid\),前者记录每个值出现的次数,后者记录当前值是否出现,即 \(forbid[i] = (cnt[i] > 0)\),前缀和维护 \(forbid\) 数组。注意去除贡献时不需要修改前缀和数组。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;int main(void) {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t;cin >> t;while (t--) {int n;cin >> n;vector<int> forbid(2 * n + 1);vector<int> cnt(2 * n + 1);vector<array<int, 2>> a(n);auto add = [&](int x) {if (cnt[x] == 0)forbid[x] = 1;cnt[x]++;};auto remove = [&](int x) {cnt[x]--;if (cnt[x] == 0)forbid[x] = 0;};for (auto& [l, r] : a) {cin >> l >> r;if (l == r) {add(l);}}vector<int> presum(2 * n + 1);partial_sum(forbid.begin(), forbid.end(), presum.begin());auto get_sum = [&](int l, int r) {return presum[r] - (l == 0 ? 0 : presum[l - 1]);};string s(n, '0');for (int i = 0; i < n; ++i) {auto [l, r] = a[i];if (l == r) {remove(l);}auto sum = get_sum(l, r);if (sum != (r - l + 1) || forbid[l] == 0)s[i] = '1';if (l == r)add(l);}cout << s << '\n';}return 0;
}


C - Bewitching Stargazer (cf2053 C)

题目大意

一个长度为 \(n\) 的全排列,\(1,2,3,\dotsc,n\),给定 \(k\),对区间 \([1,n]\) 进行以下操作。

考虑当前区间\([l,r]\),长度\(len = r - l + 1\)

  • \(len < k\),停止。
  • \(len\) 为奇数,幸运值增加\(m = \lfloor \frac{l + r}{2} \rfloor\),考虑区间 \([l, m - 1]\)\([m + 1, r]\)
  • \(len\) 为偶数,考虑区间 \([l, m]\)\([m + 1, r]\)

问最终的幸运值是多少。初始时幸运值为 \(0\)

解题思路

  • 第一阶段,会考虑一个区间:\([1,n]\)
  • 第二阶段,会考虑两个区间:\([1,\frac{n}{2}]\)\([\frac{n}{2} + 1,n]\)
  • 第三阶段,会考虑四个区间:\([1,\frac{n}{4}]\)\([\frac{n}{4} + 1,\frac{n}{2}]\)\([\frac{n}{2} + 1,\frac{3n}{4}]\)\([\frac{3n}{4} + 1,n]\)
  • 第四阶段,会考虑八个区间:\([1,\frac{n}{8}]\)\([\frac{n}{8} + 1,\frac{n}{4}]\)\([\frac{n}{4} + 1,\frac{3n}{8}]\)\([\frac{3n}{8} + 1,\frac{n}{2}]\)\([\frac{n}{2} + 1,\frac{5n}{8}]\)\([\frac{5n}{8} + 1,\frac{3n}{4}]\)\([\frac{3n}{4} + 1,\frac{7n}{8}]\)\([\frac{7n}{8} + 1,n]\)
  • ......(上述可能会因为奇数的原因有所偏差)
  • 区间长度 \(< k\),停止。

最终的幸运值就是每个阶段的贡献和。

注意到,无论是哪个阶段,所考虑的区间的长度都是一样的:从 \(n\),变成了 \(\frac{n}{2}\),再变成了 \(\frac{n}{4}\),再变成了 \(\frac{n}{8}\)...,这意味着,如果当前阶段的区间长度是奇数,那么它们对幸运值都有贡献,区别只是偏移量不同。

即如果第二阶段的区间长度是奇数,那么第二阶段的两个区间都会对幸运值有贡献,区别只是偏移量不同:第一个区间的贡献是 \(m\),第二个区间的贡献则是 \(shift + m\)。而这个偏移量 \(shift\) 是来自第一阶段的中点。

从中点值、偏移量的角度考虑每个阶段的贡献部分,则可以分成两个部分:中点值 \(\times\) 区间数量 + 总偏移量和。

对于当前的第 \(i\) 阶段,区间长度是 \(n\),则中点值是 \(\frac{n + 1}{2}\),区间数量是 \(2^{i - 1}\),考虑总偏移量和怎么求,从上述举例的分析可以得知它可以从上一阶段的总偏移量和中得到。

\(i - 1\) 阶段的总偏移量和是 \(add[i - 1]\),考虑每个偏移量的来源,像上述的第二阶段中的 \(shift\),它作用在区间\([\frac{n}{2} + 1,n]\)上,到了第三阶段,该区间就分成两个区间,每个区间算贡献时都要带上这个偏移量,因此该偏移量的贡献就会翻倍。由此实际上每个偏移量的贡献都会翻倍,即 \(add[i] = 2 \times add[i - 1]\)

同时还有新的偏移量出现:考虑区间\([1,\frac{n}{2}]\),到了第 \(i\) 阶段,该区间会被一分为二,第二个区间就新增偏移量 \(\frac{n}{2}\),而一共有 \(2^{i - 1}\) 个区间,因此新增的偏移量和就是 \(2^{i - 1} \times \frac{n}{2}\)

因此,从第 \(i - 1\) 阶段到第 \(i\) 阶段,总偏移量和就是 \(add[i] = 2 \times add[i - 1] + 2^{i - 1} \times \frac{n}{2}\)。注意这里的\(n\)是当前阶段的区间长度,不是题目给定的\(n\)

由此,当前阶段的贡献:中点值 \(\times\) 区间数量 + 总偏移量和,即 \(\frac{n + 1}{2} \times 2^{i - 1} + add[i]\),都能\(O(1)\)得到,而阶段数是\(O(\log n)\)的,因此一次询问的时间复杂度是\(O(\log n)\)的。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = unsigned long long;int main(void) {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);auto solve = [&](int n, int k) {LL ans = 0;__int128 nn = 1;__int128 add = 0;while (n) {if (n < k)break;if (n & 1) {ans += (n + 1) / 2 * nn + add;}add = add * 2 + (n + 1) / 2 * nn;nn *= 2;n /= 2;}return ans;};int t;cin >> t;while (t--) {int n, k;cin >> n >> k;LL ans = solve(n, k);cout << ans << '\n';}return 0;
}


D - Refined Product Optimality (cf2053 D)

题目大意

给定两个数组 \(a,b\),长度均为 \(n\)

可以打乱数组\(b\),最大化 \(\prod_{i=1}^{n} min(a_i, b_i)\)

维护 \(q\) 次操作,每次操作,将让 一个数组的一个元素加一,然后回答其最大值。

结果对 \(998244353\) 取模。

解题思路

首先考虑如何最大化乘积。可以发现,两者都从小到大排序时,乘积最大。

可以这么考虑:我们从所有数的大到小考虑,对于每个数,比如是 \(a_i\),如果它对答案有贡献,那么要从 \(b\) 中找到一个数 \(b_j\),使得 \(b_j \ge a_i\),这样才能使得 \(min(a_i,b_j) = a_i\),如果没有这样的数,它对答案没有贡献,我们就把它放在 \(a\)的池子里。接下来考虑的数比如 \(b_i\),如果它对答案有贡献,那么要从 \(a\) 中找到一个数 \(a_j\),使得 \(a_j \ge b_i\),这样才能使得 \(min(a_j,b_i) = b_i\),而此时 \(a\) 的池子里的数一定都比 \(b_i\) 大,因此我们可以直接取出来与 \(b_i\) 匹配。

即维护两个数组,一个是 \(a\) 的池子,一个是 \(b\) 的池子,然后从大到小考虑,如果当前数对答案有贡献,就从另一个数组的池子里找一个数匹配,否则就放到自己的池子里。容易发现这样匹配的结果,就是两个数组从小到大排序的结果。

知道最优情况怎么求了,接下来考虑如何维护操作。

每次操作只会让一个数加一,看着变化不大,容易想到一个朴素的维护方法:加一后,与右边的数比较,如果比右边的数大,就交换,直到不再比右边的数大为止。交换的同时维护答案。但这样的时间复杂度有问题。

考虑数组\(a\)\(1,1,1,1,1,1,2\),第一次操作将第一个数加一,则变成\(1,1,1,1,1,2,2\),经历了\(O(n)\)次交换,然后再让第一个数加一,变成\(1,1,1,1,2,2,2\),又经历了\(O(n)\)次交换,这样的时间复杂度是\(O(qn)\),会超时。

怎么办呢?考虑上述朴素做法的问题:当我们第一个数加一时,不断交换位置时,其实答案是不变的:因为交换的两个数是同一个值。而从操作前和操作后的结果对比,其实它等价于让当前数的最右边的数加一,就没了

因此,对于当前操作,假设对数组\(a\)操作,以及一个排了序的数组\(sorta\),当前对\(a_i = p\)加一,它等价于在\(sorta\)中找到最右边的\(sorta_i = p\),然后让\(sorta_{i}\)加一,同时维护答案(即先除以原来的数,操作后再乘以新的数)。

如何找到最右边的\(sorta_i = p\)呢?因为\(sorta\)是排好序的,可以用二分查找,upper_bound的前一个就是最右边的\(p\)

这样的时间复杂度是\(O(q\log n)\)的。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;const int mo = 998244353;int qpower(int a, int b) {int qwq = 1;while (b) {if (b & 1)qwq = 1ll * qwq * a % mo;a = 1ll * a * a % mo;b >>= 1;}return qwq;
}int inv(long long x) { return qpower(x, mo - 2); }int main(void) {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t;cin >> t;while (t--) {int n, q;cin >> n >> q;array<vector<int>, 2> a{vector<int>(n), vector<int>(n)};for (auto& x : a)for (auto& y : x)cin >> y;auto sorta = a;for (auto& x : sorta)sort(x.begin(), x.end());LL ans = 1;auto get = [&](int x) { return min(sorta[0][x], sorta[1][x]); };for (int i = 0; i < n; ++i) {ans = ans * get(i) % mo;}cout << ans << ' ';while (q--) {int o, x;cin >> o >> x;--o;--x;int val = a[o][x];int pos = prev(upper_bound(sorta[o].begin(), sorta[o].end(), val)) -sorta[o].begin();ans = ans * inv(get(pos)) % mo;a[o][x] = val + 1;sorta[o][pos] = val + 1;ans = ans * get(pos) % mo;cout << ans << " \n"[q == 0];}}return 0;
}


E - Resourceful Caterpillar Sequence (cf2053 E)

题目大意

给定一棵树,对于一对点\((p, q)\),定义一个毛毛虫序列:\(p \to q\)的路径上的点,其中\(p\)是毛毛虫的头,\(q\)是毛毛虫的尾。

两人博弈,先手拉着毛毛虫的头,后手拉着毛毛虫的尾,两人轮流执行操作。每次可以选择一个点,然后将毛毛虫的头或尾连同身子往前移动到这个点的子节点上。

如果头到了叶子节点,先手胜利,如果尾到了叶子节点,后手胜利,如果始终无法到达叶子节点,平局。

两人都会采取最优策略,问\((p,q)\)的数量,使得后手必胜。

解题思路

考虑后手必胜的情况,容易想到的一个是:

  • \(q\)在叶子节点,\(p\)不在叶子节点,那么后手必胜。这个统计一下叶子数量即可得到。

还有一种情况是,先手移动后,\(q\)在一个特别的位置,它可以一步移动到叶子节点,这样后手必胜。

我们定义能一步到叶子节点的点为好点,对于\(q\)点,考虑其每一个邻居\(u\),如果\(u\)好点,那么\((u,q)\)这对点对答案有贡献,考虑贡献怎么求。

由于先手移动一步后,\(q \to u\),那么\(p\)得在\(u\)的子节点上,这样先手移动才有\(q \to u\),考虑\(p\)的取值:

  • 不能在叶子上
  • 不能在好点
  • 其他点都可以

因此就是统计一个子树里非叶子非好点的点的数量。一个简单的计数问题,可以用DFS解决。

选定一个点为根后,对于每个点\(q\),考虑其儿子\((u,q)\),还有父亲\((fa, q)\),计算它们的贡献即可。

时间复杂度是\(O(n)\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;int main(void) {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t;cin >> t;while (t--) {int n;cin >> n;vector<vector<int>> edge(n);vector<int> du(n);for (int i = 0; i < n - 1; ++i) {int u, v;cin >> u >> v;--u, --v;edge[u].push_back(v);edge[v].push_back(u);du[u]++;du[v]++;}if (n == 2) {cout << 0 << '\n';continue;}int leaves = 0;vector<int> good(n);for (int i = 0; i < n; ++i) {if (du[i] == 1) {leaves++;for (int v : edge[i]) {good[v] = 1 && (du[v] != 1);}}}int goods = accumulate(good.begin(), good.end(), 0);LL ans = 1ll * leaves * (n - leaves);auto dfs = [&](auto& dfs, int u, int fa) -> array<int, 3> {int sum = good[u];int sz = 1;int leave = du[u] == 1;for (int v : edge[u]) {if (v == fa)continue;auto [nsum, nleave, nsz] = dfs(dfs, v, u);if (good[v]) {ans += nsz - nleave - nsum;}sum += nsum;sz += nsz;leave += nleave;}if (u != fa && du[u] != 1) {if (good[fa]) {int ano_sum = goods - sum;int ano_sz = n - sz;int ano_leave = leaves - leave;ans += ano_sz - ano_leave - ano_sum;}}return {sum, leave, sz};};int root = 0;while (root < n && du[root] == 1)root++;dfs(dfs, root, root);cout << ans << '\n';}return 0;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/860810.html

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

相关文章

GIMP 处理 png 透明边缘

首先设置画布大小然后点击 图层 -> 图层到图像大小

时间的本质-计算视角

原作:Stephen Wolfram计算机视域下的时间 时间是人类经验的核心。然而,究竟什么是时间?在传统科学中,它常被比作空间坐标(尽管这种坐标对我们来说总是不断增长)。尽管这种描述在数学上可能很有用,但它并没有揭示时间的本质。 一旦我们开始用计算术语思考,就会觉得越来越…

中考英语优秀范文-002 Music 音乐

1 写作要求 作家雨果说过:“开启人类智慧的钥匙有三把,一是字母,二是数字,三是音符。”从这句话中,我们足可见音乐对人的发展的影响。请你根据以下提示,以Music为题,写一篇短文参加学校的英语作文竞赛。 提示: 1音乐使人充满活力,让人快乐; 2 没有音乐,生活就没有乐…

Windows11安装Linux子系统(WSL2)

1、确认BIOS中已经打开虚拟化 Virtualization Technology (我的机器默认已经打开 Enabled / Disabled,主板不同进入的地方不一样,自行搜索) 2、以管理员身份打开 PowerShell 3、输入(此步安装WSL): dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subs…

软工个人总结

结束了? 一、学期回顾 1.1 课程想象与现实 最初听闻软件工程课程,脑海中浮现的是一群程序员在电脑前敲打着代码,创造出一个个功能强大的软件。想象着自己能通过这门课,精通多种编程语言,独立开发出令人惊艳的应用程序。可是在第一次编程作业布置下来时,发现自己什么也不会…

UML之关联

关联指两个类之间的各种联系。UML使用各种单实线表示关联,但关联所能够表达的信息远不只是一条实线所传递的依赖。关联指两个类之间的各种联系。UML使用各种单实线表示关联,这个单实线可以是直线(垂直的、水平的或者倾斜的)、折线甚至曲线。 事实上,关联也是展示类的属性的…

代码随想录——动态规划13.分割等和子集

思路 难点 我只想到了:“找一个子集,每个数取或不取求其和,看是否和另一个子集的和相等 ” 但是实际上既然是两个子集相等,那么只要和等于 sum/2 即可了! 取或不取用01背包,但是不知道怎么用。 只有确定了如下四点,才能把01背包问题套到本题上来。背包的体积为sum / 2 背…

深度解析 Transformer 模型中的位置嵌入(Positional Embedding)

在自然语言处理中,词语的顺序对句子的意义至关重要。然而,传统的自注意力机制无法区分词语的位置。本文深入浅出地介绍了**位置嵌入(Positional Embedding)**的概念及其在Transformer模型中的作用,解释了它如何帮助模型理解词语的顺序,从而提升文本处理的准确性。通过简单…

java8--方法--格式化输出--printf--索引

System.out.printf("%1$s %2$tB %2$te %2$tY","Due date",new Date()); 效果图:ps: 1.一个字符串需要有多个格式化单词,通过建立索引实现,索引值用%$包围,$后紧跟格式化的目标类型,后面按顺序传入变量或填写内容 2.t指定日期类型,b指定填充月份的完…

前端重学之Number

Number (尾附IEEE754解读) mdn文档 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Numberjs里的number是双进度浮点数 用IEEE745 编码 0b 0o 0x 分别表示 二进制 八进制 十六进制```js 0.tostring() //报错 0 .tostring()//正确 ```IEEE7…

DVWA靶场搭建及错误解决教程

前言 DVWA(Damn Vulnerable Web Application)靶场是一个旨在帮助安全人员和开发人员学习和提高网络安全技能的开源项目。它是一个故意存在多种安全漏洞的 PHP/MySQL 网络应用程序,通常用于学习和测试各种网络攻击技术 工具下载链接:https://pan.quark.cn/s/49ef556eb32b 搭…

招行面试:万亿GB网盘, 从0到1设计,如何实现?

本文原文链接 文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备 免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 …