Loading

题目归档 #4

目录

  • CF578C Weakness and Poorness
  • CF1285D Dr. Evil Underscores
  • CF1242C Sum Balance

CF578C Weakness and Poorness

一道基础算法题目。

题意

给定一个长度为 \(n\) 的数组 \(a_i\),求一个实数 \(x\),使得序列 \(a_1-x,a_2-x,...,a_n-x\) 的不美好程度最小。不美好程度定义为一个序列的所有连续子序列的糟糕程度的最大值。糟糕程度定义为一个序列的所有位置的和的绝对值。

  • \(n \leq 2 \times 10^5\)

解题

第一眼看到可能会想到二分答案 \(x\),但仔细一看 \(x\) 并不单调。再观察一会儿, \(x\) 貌似是个凸函数,这给三分提供了可能。

但我们必须要证明这是个凸函数,怎么证明呢?

我们知道,凸函数有一个奇妙♂的性质,即若 \(f(x)\)\(g(x)\) 是凸函数,\(h(x)=\max(f(x),g(x))\) 也是凸函数。

显然我们可以看到,对于这个序列的子序列,他们的「糟糕程度」肯定是一个凸函数。由于整个序列的「不美好程度」是「糟糕程度」的最大值,所以「不美好程度」也是凸函数。

因此三分 \(x\),最后用喜闻乐见的解决「最大子段和」的方法求出当时的「不美好程度」,这道题就解决了。

程序

#include <iostream>
#include <cstring>
#include <cstdio>

#define Maxn 200010

using namespace std;

const double eps = 1e-8;

int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while('0' <= c && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

int N;
double a[Maxn], b[Maxn], f[Maxn];

double calc(double x) {
	memset(f, 0, sizeof(f));
	for(int i = 1; i <= N; ++i) b[i] = a[i] + x;
	double ans = 0, res = 0;
	for(int i = 1; i <= N; ++i) {
		if(res + b[i] > 0) res += b[i];
		else res = 0;
		ans = max(ans, res);
	}
	
	for(int i = 1; i <= N; ++i) b[i] = -b[i];
	res = 0;
	for(int i = 1; i <= N; ++i) {
		if(res + b[i] > 0) res += b[i];
		else res = 0;
		ans = max(ans, res);
	}
	return ans;
}

int main() {
	N = read();
	for(int i = 1; i <= N; ++i) a[i] = read();
	double l = -20000, r = 20000;
	for(int cnt = 1; cnt <= 200; ++cnt) {
		double mid = (l + r) / 2;
		double q1 = calc(mid - eps), q2 = calc(mid + eps);
		if(q1 > q2) l = mid;
		else r = mid;
	}
	printf("%.7lf", calc(l));
	return 0;
}

CF1285D Dr. Evil Underscores

一道基础 01 trie 题目。

题意

有一个长度为 \(n\) 的整数序列 \(a_1,\cdots,a_n\),你需要找到一个非负整数 \(X\) 使得 \(\max(a_i\oplus X)\) 最小,其中 \(\oplus\) 为按位异或运算。输出这个最小值。

  • \(n \leq 10^5,a_i \leq 2^{31}-1\)

解题

考虑建立一棵 01-trie。注意要从最高位开始建,较小的数字需要用前导零来补全。

然后从 01-trie 向下递归计算。遇到只有一个儿子的节点,显然此时 \(X\) 这一位肯定和这个节点相同,因为这样异或起来为 \(0\)。遇到两个儿子的节点有点麻烦,此时要用到树形 dp 的思想,因为你必须要做出取舍。首先 \(X\) 的这一位肯定是保不住了,因此这一位上的答案为 \(1\)。此外,为了使接下来的答案最小,只需要在它的两个儿子中取 \(\min\) 即可。

程序

#include <iostream>
#include <cstring>
#include <cstdio>

#define Maxn 100010
#define Maxp 3000010

using namespace std;

int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while('0' <= c && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

int N, node[Maxp][2], tot = 1;
int a[31];

void trie() {
	int num = read(), pos = 0;
	memset(a, 0, sizeof(a));
	while(num) {
		a[++pos] = num & 1;
		num >>= 1;
	}
	int p = 1;
	for(int i = 30; i >= 1; --i) {
		if(node[p][a[i]]) p = node[p][a[i]];
		else p = node[p][a[i]] = ++tot;
	}
}

int dfs(int p, int depth) {
	if(!node[p][0] && !node[p][1]) return 0;
	else if(node[p][0] && !node[p][1]) return dfs(node[p][0], depth + 1);
	else if(!node[p][0] && node[p][1]) return dfs(node[p][1], depth + 1);
	else return min(dfs(node[p][0], depth + 1), dfs(node[p][1], depth + 1)) + (1 << (29 - depth));
}

int main() {
	N = read();
	for(int i = 1; i <= N; ++i) trie(); 
	cout << dfs(1, 0) << endl;
	return 0;
}

CF1242C Sum Balance

一道龌龊的细节题目加上一定的算法难度,虽然并没有涉及多么高深的算法。

题意

\(k\) 个盒子,每个盒子里有一堆数,第 \(i\) 堆有 \(n_i\) 个数,所有盒子的数都互不相同。
你现在需要从每个盒子里拿出一个数,打乱后再每个盒子放回去一个。问是否有方案使得最后每个盒子里的数的和相等?如果有同时还需要给出方案。

  • \(k\leq 15, n_i \leq 5000\),数字大小的绝对值在 \(10^9\) 以内。

解题

首先我们显然可以证明,从某个盒子取出一个数字 \(x\),那么再放入这个盒子的数字 \(y\) 是一定的,毕竟所有盒子的数都互不相同。

那么显然第一反应可能有这么一个思路:我先确定一个必须要换数字的盒子,枚举取出的数字 \(x\),然后就可以得出放入的数字 \(y\)。紧接对于那个 \(y\) 所在的盒子,被取出了一个 \(y\),那么肯定还要放入一个数字 \(z\)。而这些都是可以求出的,最后形成一个「环」回到 \(x\),说明找到了一种方案。

我开始也是这么想的,这样的思路其实已经接近正解,然而我们忽略了一个问题——这样的「环」可能存在多个,于是我们必须要把这道题转换成一个便于解决的图论模型。

考虑对于每个数字建一个点,然后连向另外一个与之匹配的点(就像刚才的 \(x\)\(y\) 那样匹配:我取出了这个数字后,就要再向它所在的盒子里装一个确定的数字)。这样会形成一个有 \(N\) 个点 \(N\) 条边的图。显然,这是一个由内向树和树组成的一个乱七八糟的森林。我们现在要把其中的环揪出来,可以使用 topsort,在此不再赘述。

把环找出来后仍有一个问题——我们要保证所有的箱子里都取出了数字(对于不用取数字的箱子,我们可以视为取出来又放回去),而每一个环可能只对应了若干个箱子,还可能有重复。

这时注意到数据范围 \(k \leq 15\),显然想到状态压缩。我们把每个环涉及到的箱子压成一个最多仅 \(15\) 位的二进制数(我们称其为基础箱子序列),其中涉及到的箱子那一位就为 \(1\)。由于只需要给出任意一种方案,现在我们只需要解决:如何把不同的环对应的基础箱子序列拼凑在一起,形成一个更大的合并的箱子序列,最后得到一个 \(k\) 位全是 \(1\) 的二进制串(即所有箱子都取出又放回了数字)。

开始想到了爆搜,但这样复杂度最坏会达到 \(15!\),不能接受。因此考虑优化,搜索的宿敌是什么——dp!

我们在第一维枚举状态(箱子序列),第二维枚举它的子集。我们现在只需要考虑这个第一维的箱子序列能否由第二维的子集和它在第一维箱子序列下的补集拼凑而成,注意此时子集和补集都已经在第一维被枚举过了。

显然写出 dp 方程:其中 \(f[i]\) 有值当且仅当 \(i\) 是基础箱子序列,\(vis[i]\) 有值当且仅当 \(i\) 是箱子序列。

for(int i = 0; i < (1 << K); ++i) {
		if(f[i]) vis[i] = 1;
		for(int j = i; j; j = (j - 1) & i) {
			if(vis[j] && vis[i ^ j]) {
				vis[i] = 1;
				bef[i] = j;
			}
			if(vis[i]) break;
		}
	}

\(bef\) 数组存储了当前箱子序列 \(i\) 的一个子集,便于之后的输出。

输出方案部分,只需要递归处理。如果当前 \(f[i]\) 有值,说明是基础箱子序列,就要把它对应的环的信息处理后输出。如果没有值,说明这个箱子序列是合并出来的,根据 \(bef\) 数组递归解决即可。细节参见代码。

程序

#include <iostream>
#include <cstring>
#include <cstdio>
#include <map>

#define Maxk 16
#define Maxn 750010
#define LL long long

using namespace std;

int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while('0' <= c && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

LL K, N, tot, sum[Maxk];
LL deg[Maxn], f[1 << Maxk], bef[1 << Maxk];
bool vis[1 << Maxk];

struct Edge {
	int next, to;
}
edge[Maxn];
int head[Maxn], edge_num;

struct Point {
	LL val, bel;
}
point[Maxn];
map <LL, LL> check;

void add_edge(int from, int to) {
	edge[++edge_num].next = head[from];
	edge[edge_num].to = to;
	head[from] = edge_num;
}

void fl(int first, int u, int sta) {
	if(u == first && !deg[u]) {f[sta] = first; return;}
	deg[u] = 0;
	int nowu = 1 << (point[u].bel - 1);
	if(sta & nowu) return;
	fl(first, edge[head[u]].to, sta | nowu);
}

int stack[Maxn], top;
void topsort() {
	for(int i = 1; i <= N; ++i) if(!deg[i]) stack[++top] = i;
	while(top) {
		int u = stack[top--];
		int v = edge[head[u]].to;
		--deg[v];
		if(!deg[v]) stack[++top] = v;
	}
	for(int i = 1; i <= N; ++i) if(deg[i]) fl(i, i, 0);
}

LL out1[Maxk], out2[Maxk];

void output(int sta) {
	if(f[sta]) {
		int u = f[sta];
		while(true) {
			int v = edge[head[u]].to;
			out1[point[v].bel] = point[v].val;
			out2[point[v].bel] = point[u].bel;
			u = v;
			if(u == f[sta]) break;
		}
	}
	else {
		output(bef[sta]);
		output(sta ^ bef[sta]);
	}
}

int main() {
	K = read();
	LL size, val;
	for(int i = 1; i <= K; ++i) {
		size = read();
		for(int j = 1; j <= size; ++j) {
			val = read();
			point[++N].val = val, point[N].bel = i;
			sum[i] += val, tot += val; 
		}
	}
	if(tot % K) {printf("No"); return 0;}
	else tot = tot / K;
	for(int i = 1; i <= N; ++i) check[point[i].val] = i;
	for(int i = 1; i <= N; ++i) {
		int u = i, v = check[tot - sum[point[i].bel] + point[i].val];
		if(!v) continue;
		add_edge(u, v); ++deg[v];
	}
	topsort();
	for(int i = 0; i < (1 << K); ++i) {
		if(f[i]) vis[i] = 1;
		for(int j = i; j; j = (j - 1) & i) {
			if(vis[j] && vis[i ^ j]) {
				vis[i] = 1;
				bef[i] = j;
			}
			if(vis[i]) break;
		}
	}
	if(vis[(1 << K) - 1]) {
		printf("Yes\n");
		output((1 << K) - 1);
		for(int i = 1; i <= K; ++i) printf("%lld %lld\n", out1[i], out2[i]);
	}
	else printf("No");
	return 0;
}
posted @ 2020-08-02 13:59  Sqrtyz  阅读(116)  评论(0)    收藏  举报