NOIP2021 备考 Day1

粉刷宿舍

题目描述

金秋九月,yukiii 来到了大学校园,开启一段全新的生活。
但在此之前,yukiii 还要将年久失修的宿舍进行翻新。
现在他和友正粉刷的墙壁。
宿舍的墙壁可以抽象为一个有限但足够大网格,中部分都已经被 yukiii 的舍友们粉刷完毕,只剩下 n 列尚未粉刷,且在第 i 列中未被粉刷的 部分是从地面开始向上的长为 \(a_i\) 的连续段。
刷墙是一件十分枯燥且消耗体力的工作,因此 yukiii 也会采用一个十分简单粗暴的方式完成这项工作:将刷子置于某一个格当中,然后从上、下左、右中选择一个方 向一直刷下去。 yukiii 将这称作是一次操作 。
但 yukiii 的舍友很快便对他这种工作方式感到反感: yukiii 经常会用自己的刷子扫到已经被粉过部分,这会导致干掉油漆再次湿十影响美观。因此 yukiii 的舍友 HHC 要求 yukiii 在刷墙的过程中不能触碰到其他人已经粉刷过的部分。
现在 yukiii 想知道自己至少需要几次操作才能在不碰到别人已经刷过的部分。

输入格式

第一行个整数 \(n\) ,表示需要被粉刷的列数;
接下来一行有 \(n\) 个整数,分别表示每一列需要刷的格子数。

输出格式

输出一行个整数,表示答案。

样例输入

5
2 2 1 2 1

样例输出

3

数据范围

\(1 \leq n \leq 5e5, 0 \leq a_i \leq 1e9\)

解析

考虑使用分治。
对于一段区间,我们可以横着涂一部分,也可以竖着涂。对于竖着涂的次数,显然是区间长度;而对于横着涂,首先会花费当前区间的最小值次,然后将区间分成了若干段,再加上这若干段的花费。当前段的花费即为竖着涂和横着涂的最小值。
问题在于如何划分区间。考场上以为是线段树维护最小值,0的位置,区间减。 其实这个可以用 vector 维护。记 pos[i] 存储权值为 i 的点的位置,这样就可快速得到最小值的位置,再用 st 表计算出区间最小值。

代码

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;

const int MAXN = 5e5 + 5;

int n, len, a[MAXN], b[MAXN];
int lg[MAXN], st[MAXN][21];
vector<int> pos[MAXN];

void ST_prework() {
	lg[0] = lg[1] = 0; lg[2] = 1;
	for(int i = 3; i <= n; i++) lg[i] = lg[i / 2] + 1;
	for(int i = 1; i <= n; i++) st[i][0] = a[i];
	for(int j = 1; j <= lg[n]; j++)
	 for(int i = 1; i <= n - (1 << j) + 1; i++)
	  st[i][j] = min(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
}

int ST_query(int l, int r) {
	int k = lg[r - l + 1];
	return min(st[l][k], st[r - (1 << k) + 1][k]);
}

int solve(int lf, int rg, int val) {
	if(lf > rg) return 0;
	if(lf == rg) return 1;
	int v = val;
	val = ST_query(lf, rg);
	int ccc = lower_bound(pos[val].begin(), pos[val].end(), lf) - pos[val].begin();
	int res = b[val] - b[v], las = lf, i;
	for(i = ccc; pos[val][i] <= rg && i < pos[val].size(); i++) {
		res += solve(las, pos[val][i] - 1, val);
		las = pos[val][i] + 1;
	}
	res += solve(las, rg, val);
	return min(rg - lf + 1, res);
}

int main() {
	freopen("paint.in","r",stdin);
	freopen("paint.out","w",stdout);
	scanf("%d",&n);
	for(int i = 1; i <= n; i++)
	 scanf("%d",&a[i]), b[i] = a[i];
	sort(b + 1, b + n + 1);
	len = unique(b + 1, b + n + 1) - b - 1;
	for(int i = 1; i <= n; i++) {
		a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b;
		pos[a[i]].push_back(i);
	}
	ST_prework();
	printf("%d\n",min(n, solve(1, n, 0)));
	return 0;
}
/*
5
2 2 1 2 1
*/

入学考试

题目描述

进入大学后第一件比较重要的事情就是考试。 yukiii 为了确保自己顺 利通过测试在暑假刻苦学习。
在学习代数的过程中,他知道了二元运算集合上封闭性。但这并不能 难倒他,于是 yukiii

便提出了一个全新的概念:反封闭。
对于一个数集 𝑆 ⊂ 𝑁+,我们称其是 反封闭 反封闭 的当且仅:

  1. 1 ∈ 𝑆(𝑥);
  2. 对于任意 1 ≠ 𝑚 ∈ 𝑆,一定存在 𝑎, 𝑏 ∈ 𝑆 使得 m = a + b。
    现在 yukiii 想得到一个最大元素恰好为 2n−1 的反封闭集,同时他希望 这个集合中的元素数不超过 𝐾。

数据范围

\(n=1000, k=1014\)

解析

构造题

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int n, m, ans;
string s[1000005];

void dfs(int now) {
	if(now == 1) return ; ++ans;
	for(int i = 0; i < n - now; i++) s[ans] += '0';
	for(int i = n - now; i < n; i++) s[ans] += '1';
//	cout << s[ans] << endl;
	if(now & 1) {
		++ans;
		int val = now / 2;
		for(int i = 0; i < n - val - 1; i++) s[ans] += '0';
		for(int i = n - val - 1; i < n; i++) s[ans] += '1';
//		cout << s[ans] << endl;
		++ans;
		for(int i = 0; i < n - val - 1; i++) s[ans] += '0';
		for(int i = n - val - 1; i < n - 1; i++) s[ans] += '1';
		s[ans] += '0';
//		cout << s[ans] << endl;
		int pos = n - 2;
		while(val) {
			++ans; s[ans] = s[ans - 1];
			s[ans][pos] = '0'; s[ans][pos - now / 2] = '1';
//			cout << s[ans] << endl;
			--pos; --val;
		}
		dfs(now / 2);
	}
	else {
		++ans;
		int val = now / 2;
		for(int i = 0; i < n - val - 1; i++) s[ans] += '0';
		for(int i = n - val - 1; i < n - 1; i++) s[ans] += '1';
		s[ans] += '0';
		--val; int pos = n - 2;
		while(val) {
			++ans; s[ans] = s[ans - 1];
			s[ans][pos] = '0'; s[ans][pos - now / 2] = '1';
			--pos; --val;
		}
		dfs(now / 2);
	}
}

int main() {
	freopen("exam.in","r",stdin);
	freopen("exam.out","w",stdout);
	scanf("%d%d",&n, &m);
	dfs(n);
	printf("%d\n",ans + 1);
	++ans;
	for(int i = 0; i < n - 1; i++) s[ans] += '0';
	s[ans] += '1';
	sort(s + 1, s + ans + 1);
	for(int i = 1; i <= ans; i++) cout << s[i] << endl;
	return 0;
}

选礼物

题目描述

刚开学,新生舞会自然是同们最期待的环节之一。作为幕后工人员 的 yukiii 和 HHC 被分配到了购买新生舞会纪念礼品的任务。
在主办方事先提供的礼品备选列表上共有 n 组礼品,其中第 i 组礼品的 价格为 \(c_i\),足够发给 \(a_i\) 个人,只要有一收到该种类礼品就期望能获得 \(b_i\) 的满意度,但是卖家规定只有购买了第 \(d_i\) 组礼品才能买这( \(d_i=0\) 则表示无此限制)。 则表示无此限制)。
现在已知有 m 位同学会来参加新生舞, yukiii 和 HHC 需要在保证人手 至少一份礼物的情况下最大化期望满意度和买所消耗钱比值。
他们两个对这问题都毫无办法,因此只能来求助于精通各种最优化的你了。

输入格式

输入第一行包含两个正整数 n,m,分别 表示可供选择的礼品数和期望参加舞会的人数。
接下来 n 行, 每行包含四个正整数 \(a_i,b_i,c_i,d_i\),表示第 i 组礼品的相关信息 。

输出格式

输出一个实数,表示期望满意度和买礼物所消耗的钱比值最大。
你的答案只有在与标准相对误差或绝小于 1e−3 时才会被判为对。

样例输入

5 10
1 8695 30594 0
7 9390 3109 1
8 30425 17108 1
6 24713 14066 1
6 365 18488 2

样例输出

1.129

数据范围

\(n \leq 1000, m \leq 1000, 0 \leq d_i \leq n, 1 \leq a_i \leq m, 1 \leq b_i,c_i \leq 32768\)
数据保证至少存在一种合法的购买方案,且每组礼品都不会直接或间依赖自身。

解析

01分数规划,树形背包优化。
首先要求最优化比值 \(\frac{\sum_{i \in S}b_i}{\sum_{i \in S}c_i}\),使用二分转化为判断能否使 \(\sum_{i \in S}(b_i-mid*c_i) \geq 0\)
由于题目有树形的依赖关系且要求 \(\sum_{i \in S}a_i \geq m\),考虑使用树形背包, 但是需要两个优化。
第一是对空间的优化,即将大于 \(m\) 的空间限制全部压到 \(m\)
第二是对时间的优化。普通的树形背包是 \(O(nm^2)\) 的,此处需要 \(O(nm)\) 的。可以使用前缀(或后缀)优化。
具体而言,设当前正在处理节点 x,它的前缀序号是 i,子树大小是 \(siz_x, w_x = b_x - mid*c_i\)
\(f[x-siz[x]]\)转移过来,即不选 \(x\) 的值,因为不选 x,则 x 的子树也都不能选。从 \(f[x-1]\) 转移过来,即选 x 的值,因为 \(f[x-1]\) 代表了 x 的所有子树的值。
所以有 \(f[x][j] = max{f[x-siz[x]][j], f[x-1][j-a[x]]+w[x]}\)
对于压到 \(m\) 的部分,设 \(tmp = max_{m-a_x \leq j \leq m}{f[x-1][j] + w[x]}\)\(f[x][m] = max{f[x-siz[x]][m], tmp}\)

代码

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;

const int MAXN = 1e3 + 5;

int n, m, root;
int a[MAXN], d[MAXN], b[MAXN], c[MAXN];
int tot, hd[MAXN], nt[MAXN], to[MAXN];
int dfn, siz[MAXN];
double w[MAXN], f[MAXN][MAXN];

void add(int x, int y) {
	to[++tot] = y, nt[tot] = hd[x], hd[x] = tot;
}

void dfs(int now) {
	siz[now] = 1;
	for(int i = hd[now]; i; i = nt[i])
	 dfs(to[i]), siz[now] += siz[to[i]];
	int x = ++dfn;
	double tmp = -1e100;
	for(int j = max(0, m - a[now]); j <= m; j++) tmp = max(tmp, f[x - 1][j]);
	f[x][m] = max(tmp + w[now], f[x - siz[now]][m]);
	for(int j = m - 1; j >= 0; j--)
	 if(j < a[now]) f[x][j] = f[x - siz[now]][j];
	 else f[x][j] = max(f[x - 1][j - a[now]] + w[now], f[x - siz[now]][j]);
}

bool check(double mid) {
	for(int i = 1; i <= n; i++) w[i] = 1.0 * b[i] - c[i] * mid;
	dfn = 0;
	memset(f[0] + 1, 0xc2, sizeof(double)*m);
	f[0][0] = 0;
	dfs(0);
	return f[dfn][m] > 1e-18;
}

int main() {
	freopen("gift.in","r",stdin);
	freopen("gift.out","w",stdout);
	scanf("%d%d",&n, &m);
	double l = 0, r = 0.0, mid;
	for(int i = 1; i <= n; i++) {
		scanf("%d%d%d%d",&a[i], &b[i], &c[i], &d[i]);
		r += b[i]; add(d[i], i);
	}
	while(r - l > 1e-4) {
		mid = (l + r) / 2;
		if(check(mid)) l = mid;
		else r = mid;
	}
	printf("%.3lf\n",mid);
	return 0;
}
posted @ 2021-09-10 20:37  zym417  阅读(49)  评论(0编辑  收藏  举报