粉刷宿舍

题目描述

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

输入格式

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

输出格式

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

解析

考场上想出了策略,但没去写,主要是我没想到去用递归形式去写避免冗杂的修改。
对于一个区间,它最坏情况下就是全部竖着刷,也就是 \(r - l + 1\) 次,但是我们可以横着刷,这样也许能更优,对于横着刷,我们显然一次要横着把最低的刷完,不然没有意义,因为如果没有刷完还要竖着把剩余的刷完。
进而我们可以发现这个区间内的矩形的相对大小是没有发生变化的,所以我们并不需要真的去修改。
如果我们递归的去实现这个过程,我们只用记录一下之前横着刷了多少就行了。
在递归的时候,我们在 \(st\) 表中存下区间最小值和它的位置,然后每次递归进入子区间处理就好了。

代码

个人感觉自己写的很妙。
本地测的时候大数据要把栈空间开大。

#include<cstdio>
#include<iostream>
#include<cctype>
#include<cmath>

using namespace std;

const int N = 5e5 + 5;

int n, a[N], m, f[N][20], p[N][20];

inline void init() {
	for(int i = 1; i <= n; i++) f[i][0] = a[i], p[i][0] = i;
	int t = log(n) / log(2) + 1;
	for(int j = 1; j < t; j++)
		for(int i = 1; i <= n - (1 << j) + 1; i++)
			if(f[i][j - 1] <= f[i + (1 << (j - 1))][j - 1]) {
				f[i][j] = f[i][j - 1];
				p[i][j] = p[i][j - 1];
			}
			else {
				f[i][j] = f[i + (1 << (j - 1))][j - 1];
				p[i][j] = p[i + (1 << (j - 1))][j - 1];
			}
}

inline int query(int l, int r, int &x) {
	int k = log(r - l + 1) / log(2);
	if(f[l][k] <= f[r - (1 << k) + 1][k]) {
		x = p[l][k]; return f[l][k];
	}
	else {
		x = p[r - (1 << k) + 1][k]; return f[r - (1 << k) + 1][k];
	}
}

int dfs(int l, int r, int v) {
	if(l > r) return 0;
	if(l == r) return a[l] == v ? 0 : 1;
	int mid, w = query(l, r, mid);
	return min(r - l + 1, dfs(l, mid - 1, w) + dfs(mid + 1, r, w) + w - v);
}

inline void read(int &x) {
	x = 0; int c = getchar();
	for(; !isdigit(c); c = getchar());
	for(; isdigit(c); c = getchar())
		x = x * 10 + c - 48;
}

int main() {
	freopen("paint.in", "r", stdin);
	freopen("paint.out", "w", stdout);
	read(n);
	for(int i = 1; i <= n; i++) read(a[i]);
	init();
	printf("%d\n", dfs(1, n, 0));
	return 0;
}
入学考试

题目描述

进入大学后第一件比较重要的事情就是考试。 \(yukiii\) 为了确保自己顺 利通过测试在暑假刻苦学习。
在学习代数的过程中,他知道了二元运算集合上封闭性。但这并不能 难倒他,于是 \(yukiii\) 便提出了一个全新的概念:反封闭。
对于一个数集 ????⊂????+,我们称其是 反封闭 反封闭 的当且仅: 的当且仅:

  1. 1∈????(????);
  2. 对于任意 1≠????∈????,一定存在 ????,????∈???? 使得 \(????=????+????\)
    现在 \(yukiii\) 想得到一个最大元素恰好为 \(2 ^ {???? − 1}\) 的反封闭集,同时他希望 这个集合中的元素数不超过 ????。

输入格式

输入包括一行两个整数 ????,????,含义如题所述。

输出格式

输出第一行包括个数 \(T\),表示你所构造的集合大小。
接下来 \(T\) 行,每一个长为 \(????\)\(01\) 串数,表 示集合中的二进制示。如果二进制不足 \(????\) 位则用前缀 \(0\) 补齐。你需要按照从小到大的顺序输出所有数。

NOIP 备考日记 Day4_i++

解析

一道比较简单的构造题。
首先看了样例之后我们能很容易的想出 \(2n - 1\) 个元素的构造方法。
但是看到后面的 \(k\),这个方法肯定不行。
而上面这个不优的原因,是因为我们每次为了构造一个数都要用掉两个数。
所以我们如果要优化的话那么尽量用一个数去构造另一个数。
因为同样的数可以用两次,所以如果我们用一个数的二分之一来表示它的话,我们就只用往集合中添加一个数。
就比如:

\[11111111 \]

\[11110000 \]

\[01111000 \]

\[00111100 \]

\[00011110 \]

\[00001111 \]

然后我们就进入了 \(1111\) 这个状态进行递归。
但是如果遇到全部为 \(1\) (不算前面补的 \(0\)), \(1\) 有奇数个的时候,我们的第一步就有问题了。
因为我们这样是为了将 \(11110000\)\(00001111\) 组合成 \(11111111\)
但是奇数的时候就不能这样。
所以我们尝试分前面 \(n/2\)\(n/2 + 1\)
然后我们就会发现,当分前面 \(n/2\) 时更优。
就比如:

\[11111 \]

\[11000 \]

\[01100 \]

\[00110 \]

\[00011 \]

这个时候我们要构造出 \(11111\), 就只能用 \(11000\)\(00111\)
所以我们必须还要加一个 \(111\) 进去。
但是这个的优点在于我们能用 \(00110\)\(00001\) 构造出它。
\(00110\)\(00001\) 都是我们所必须的,所以我们只多加了 \(00111\) 一个进去。
而且我们还将下一个状态转为了偶数个,降低了下一个状态的代价。
然后算一下我们所需要的个数。
首先对于偶数,我们的代价是 \(n/2\), 对于奇数我们的代价是 \(n/2 + 1 + 1\)
我们累加一下 \(n/2\),这个 \(n\) 其实每次是不一样的,每次缩小一半。
所以是 \(\sum_{i = 1} ^ {logn} \frac{n}{2 ^ {i - 1}}\),等比数列求和一下,这部分是趋近于 \(n\) 的。
最坏情况下每次多一。
所以我们的上界大致是 \(n + logn\)

代码

#include<cstdio>

using namespace std;

int n, k, a[1005], tot = 0;

void dfs(int x, int f) {
	if(x == 1) {
		if(f) {
			for(int i = 1; i <= n - x; i++) putchar('0');
			for(int i = 1; i <= x; i++) putchar('1');
			putchar('\n');
		} ++tot;
		return ;
	}
	dfs(x / 2, f);
	if(x & 1 && f) {
		for(int i = 1; i <= n - x / 2 - 1; i++) putchar('0');
		for(int i = 1; i <= x / 2; i++) putchar('1'); putchar('0'); putchar('\n');
		for(int i = 1; i <= n - x / 2 - 1; i++) putchar('0');
		for(int i = 1; i <= x / 2 + 1; i++) putchar('1'); putchar('\n');
	}
	if(x & 1) tot += 2;
	for(int i = n - x / 2 - 1 - (x & 1); i >= n - x; i--) {
		if(f) {
			for(int j = 1; j <= i; j++) putchar('0');
			for(int j = 1; j <= x / 2; j++) putchar('1');
			for(int j = 1; j <= n - i - x / 2; j++) putchar('0');
			putchar('\n');
		}
		++tot;
	}
	if(f) {
		for(int i = 1; i <= n - x; i++) putchar('0');
		for(int i = 1; i <= x; i++) putchar('1');
		putchar('\n');
	}
	++tot;
}

int main() {
	freopen("exam.in", "r", stdin);
	freopen("exam.out", "w", stdout);
	scanf("%d%d", &n, &k);
	dfs(n, 0); printf("%d\n", tot);
	dfs(n, 1);
	return 0;
}
选礼物

题目描述

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

输入格式

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

输出格式

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

解析

考场上没有想到 \(01\) 分数规划。这不就是 \(01\) 分数规划的板题吗。
\(ans = \frac{\sum {b_i \times x_i}}{\sum{c_i \times x_i}}\)
套路一下化成:
\(\sum {b_i - ans \times c_i} = 0\)
依赖关系发现这个就是一个树上背包,价值换成 \(b_i - ans \times c_i\),体积换成 \(a_i\)。然后套分数规划的模板就行了。
然而。。。
你发现 \(TLE\) 了。
因为我们单次朴素的树上背包是 \(nm ^ {2}\) 的,显然不能过。
所以我们只能优化树上背包,所以套一个前序优化就好了。
复杂度 \(logw_{max}nm\)

代码

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

using namespace std;

const int N = 1e3 + 5;

int n, m, a[N], b[N], c[N], siz[N], dfn[N], mp[N], cnt = 0;

int head[N], nex[N], to[N], tot = 1;

double dp[N][N], l = 0, r = 0;

inline void add(int x, int y) { nex[++tot] = head[x], to[tot] = y, head[x] = tot; }

void dfs(int x) {
	siz[x] = 1; dfn[x] = cnt, mp[cnt++] = x;
	for(int i = head[x]; i; i = nex[i])
		dfs(to[i]), siz[x] += siz[to[i]];
}

inline bool check(double mid) {
	double tmp;
	for(int i = 1; i <= m; i++) dp[n + 1][i] = -1e9;
	dp[n + 1][0] = 0;
	for(int i = n, x; ~i; i--) {
		x = mp[i], tmp = -1e10;
		for(int j = max(0, m - a[x]); j <= m; j++) tmp = max(tmp, dp[i + 1][j]);
		dp[i][m] = max(tmp + b[x] - mid * c[x], dp[i + siz[x]][m]);
		for(int j = m - 1; ~j; j--)
			if(j < a[x]) dp[i][j] = dp[i + siz[x]][j];
			else dp[i][j] = max(dp[i + siz[x]][j], dp[i + 1][j - a[x]] + b[x] - mid * c[x]);
	}
	return dp[0][m] >= -1e-8;
}

inline void read(int &x) {
	x = 0; int c = getchar();
	for(; !isdigit(c); c = getchar());
	for(; isdigit(c); c = getchar())
		x = x * 10 + c - 48;
}

int main() {
	freopen("gift.in", "r", stdin);
	freopen("gift.out", "w", stdout);
	read(n), read(m);
	for(int i = 1, fa; i <= n; i++)
		read(a[i]), read(b[i]), read(c[i]), read(fa), add(fa, i), r += b[i];
	dfs(0);
	while(l + 1e-5 < r) {
		double mid = (l + r) / 2;
		if(check(mid)) l = mid; else r = mid;
	}
	printf("%.5lf\n", l);
	return 0;
}
/*
5 10
1 8695 30594 0
7 9390 3109 1
8 30425 17108 1
6 24713 14066 1
6 365 18488 2
*/