CF R857 div.1

A

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

B

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

C

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

posted @ 2023-03-15 20:20  P32sx  阅读(42)  评论(0)    收藏  举报