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 ≠ 𝑚 ∈ 𝑆,一定存在 𝑎, 𝑏 ∈ 𝑆 使得 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;
}