CF R857 div.1

1|0A

 我们尽量构造一个数字紧凑(值域接近矩阵大小)矩阵,使每个 22 的矩阵,我们最好能想出一个通用的办法使他们异或和为 0
显然,若不考虑数字尽量不同的限制,我们朴素的构造 a ^ a ^ a ^ a,我们还可以更进一步,让他们两两不同(当然很显然,但是我们要循序渐进)
现在已经我们令每个矩阵的 22 矩阵中 a ^ b ^ a ^ b,他们显然为零那么我们让他们的差异化直接翻倍,但是这还不够,我们进一步思考
发现仅靠一维坐标已经不能构造出简单的小矩形,举个例子,在数轴中我们无法表示一对数值相等的点,但是我们若在平面直角坐标系中即可用新的坐标表示出两个 y 值相同 x 值不同的点
那么,受此启发,我们讲一个数分成两部分,左部分代表纵坐标,右部分代表横坐标,如第二列第四行我们用这样的二进制数表示, 010100,我们将他对半分,他的左边是 “010”,即“2”,右边是“100”,即“4”
那么我们对于刚刚的那个 22 矩阵,我们发现横着看是右边是 a ^ b ^ a ^ b,左边也是 a ^ b ^ a ^ b,那么两部分都是 “0”,结果必然是 “0”
非常好,我们似乎已经构造除了一种方案,使得每个矩阵都满足异或和为“0”,那么符不符合数据范围呢?
我们发现我们的时间复杂度显然是 O(nm) 的,而我们需要用的数是, 2log2(n)+log2(m) 级别的,和 nm 相近,可以处理 104 级别的数据
我们的代码只需将 j 左移 log2n 位,在加上 i 即可得到每个互不相同的数了
代码如下,代码中 n=4,m=4 时的构造方案

#include <bits/stdc++.h> using namespace std; const int N = 210; int T, n, m; int main(){ cin >> T; while(T --){ cin >> n >> m; cout << n * m << endl; for(int i = 1; i <= n; i++){ for(int j = 1; j <= m; j++) printf("%d ", (i << 10) + j); puts(""); } } return 0; } // 0000 0001 0010 0011 // 0100 0101 0110 0111 // 1000 1001 1010 1011 // 1100 1101 1110 1111

2|0B

 看一眼数据范围,猜到是 O(nlogn) ,显然选择物品的顺序是对结果无影响的,那么我们钦定 A 数组进行排序
升序排序后不难发现,如果要选择第 i 号物品给小A,那么 i+1 ~ n 的所有物品都要给小B,故我们需要知道这一段中 B 数组的最大值 maxb
我们枚举小A最后得到的价值是 Ai,但是我们发现最优答案不一定是 |Aimaxbi|,因为 1 ~ i-1 会对答案产生影响,他们可以供两人随意选择,我们假设 1 ~ i-1中 B 数组的最大值为 mmb(A数组的最大值无用)进行分类讨论:
1:maxbAi,由 max 的性质可知,我们在 1 ~ i-1 中给 小B 选择答案只可能让他们的差距更大(小B礼物的价值已经不能再变小了),那么 ans = maxbAi
2:maxbAi,那么我们在 1 ~ i-1中选择就有可能缩小两人的差距,所以两种选择都有可能是答案,故 ans = min(|Aimmb|, Aimaxb)
好了,我们该思考如何实时得到 mmb,mmb 实际上是要在一个逐渐添加新元素的集合里选出一个接近 Ai 的数,我们可以用 “lower_bound”,“upper_bound”在一个 “set” 中实时查找,同时还要随着 i 的递增添加新元素
代码如下

#include <bits/stdc++.h> #define x first #define y second using namespace std; const int N = 5e5 + 10; const int INF = 0x3f3f3f3f; int T, n, maxb[N]; pair<int, int> p[N]; int main(){ cin >> T; while(T --){ memset(maxb, 0, sizeof maxb); set<int> S; cin >> n; for(int i = 1; i <= n; i++) scanf("%d %d", &p[i].x, &p[i].y); sort(p + 1, p + n + 1); maxb[n + 1] = -INF; for(int i = n; i; i--) maxb[i] = max(maxb[i + 1], p[i].y); int ans = INF; for(int i = 1; i <= n; i++){ ans = min(ans, abs(p[i].x - maxb[i + 1])); if(i > 1 && maxb[i + 1] < p[i].x){ auto t = S.upper_bound(p[i].x); if(t != S.end()) ans = min(ans, *t - p[i].x); if(t != S.begin()){ t --; ans = min(ans, p[i].x - max(maxb[i + 1], *t)); } } S.insert(p[i].y); } cout << ans << endl; } return 0; } // 4 3 2 2 1 // 10 3 7 5 5

3|0C

 首先,容易想到对于每个数组中的数子,若在它之前有数比他还大,那么他肯定不能对答案贡献,所以将这些数都删掉
不难发现所有数组都是一个单调递增的序列,那么我们就应该考虑如何快速统计答案了
容易发现,所有数组中最大值最小的数如果不放在第一个,那么这个数组就没有作用,因为这个数组前有任意一个数组都会让他没有贡献,所以最优解一定是让每个数组都有可能有贡献,换句话说,我们多考虑一些可能有贡献的,就能更有可能得到最优解
基于以上的想法,我们按每个数组中最大值的顺序从小到大进行线性DP,我们发现后效性仅与已选择的数组中的最大值有关,于是令 f(k) 数组表示最大值(即末尾值)小于 k 的最大答案,那么我们处理到第 i 号位置时,仅需要知道比第 i 个数组的最大值小的方案的最大答案即可更新 f(k) 数组,但是我们需要更新的是 f(k ),查询时需要查询 f(1 k1),不难发现树状数组满足此性质,那么我们即可用树状数组在 O(nlogR),R为值域,的时间内完成


__EOF__

本文作者userName
本文链接https://www.cnblogs.com/P32sx-qq1309267816-tel18081238250/p/17219879.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   P32sx  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示