CSP 日照集训考试Day 1

T1

考试的时候,真是一点思路也没有,有点想推式子的感觉,但是就是推不出来。

然后打了 40 分的暴力,交完了发现有些地方写的不严谨,空间开的不对劲。然后少了 20 分。

# include <bits/stdc++.h>
using namespace std;
int n;
int a[100007], b[100007], sum[100007];
bool check (int zt) {
	vector <int> wz; wz.push_back (0);
	for (int i = 1; i <= n; ++ i) {
		if ((1 << (i - 1)) & zt) wz.push_back (i);
	}
	int m = wz.size () - 1;
	for (int i = 1; i <= m; ++ i) sum[i] = sum[i - 1] + a[wz[i]];
//	cout << sum[m] <<"|\n";
//	for (int i = m; i; -- i) sumne[i] = sumne[i + 1] + a[wz[i]];
	for (int i = 1; i <= m; ++ i) {
		if (sum[m] - sum[i] + sum[i - 1] < b[wz[i]]) return 0;
	}
	return 1;
}
int main () {
	cin >> n;
	for (int i = 1; i <= n; ++ i) cin >> a[i] >> b[i];
	int ans = 100;
	for (int i = 1; i < (1 << n); ++ i) {
		if (check (i)) {
			ans = min (ans, __builtin_popcount (i));
		}
	}
	if (ans == 100) puts ("-1");
	else cout << ans;
}

/*

3
3 5
2 1
4 3

*/

60 pts :

有这样一个式子:

\[\sum a_j - a_i ≥ b_i, ∀1 ≤ i ≤ N\\ =\sum a_j ≥ a_i + b_i, ∀1 ≤ i ≤ N \]

就是说如果(其他人的工作时间)大于这个最大的(工作时间+休息时间)的话。

那所有其他人的(工作时间+休息时间)一定也小于这个(其他人的工作时间)。

所以我们可以枚举最大的(工作时间+休息时间),然后选择的其他人要满足(工作时间+休息时间)小于这个最大值。

因为我们要让上面那个式子尽可能成立,也就是在满足选完了最大值之后,其他人要选择 a[i] 尽量大的。

所以我们先以 a[i]+b[i] 为关键字排序。

然后枚举 a[i] + b[i] 的最大值 mx 。

然后在 a[i] + b[i] <= mx 的人中选择,也就是让这些人按照 a[i] 排序。

然后从大往小去选,直到满足条件,就跳出。

100 pts :

还没看懂。

T2

考场上以为 40pts 的 DP 其他人都会做,想多了…

40 pts :

首先要会 \(O (n^4)\) 的算法求最长公共子序列。

其实一开始我也忘了最长公共子序列怎么求了…就连这个四次方的算法都想了十分钟。

但是其实假如我记得平方算法怎么做的,我也不一定做出来…

考虑怎么才能对公共子序列的长度产生贡献。

只有当 \(a\) 序列与 \(b\) 序列对应相等的时候。

所以可以设计一个如下状态。

就是 \(f[i][j]\) 表示 \(a,b\) 序列分别以 \(i,j\) 位结尾的时候的最长公共子序列长度。

那考虑这个 \(f[i][j]\) 的答案可以从什么地方转移过来。

就是 \(a,b\) 序列分别在 \(i,j\) 的前面找到 \(k,l\) ,使 \(a[k] == b[l] , a[i] == b[j]\)

那,这个 \(f[i][j]\) 就可以从 \(f[k][l]\) 转移过来。

也就是 \(f[i][j] = f[k][l] + 1\)

但是要注意,这个 \(f[i][j]\) 可能还能从别的地方转移, 所以记录答案的方式是取 \(max\) ,即 \(f[i][j] = max (f[i][j], f[k][l] + 1)\)

所以这个就是四次方求公共子序列的算法,非严格,因为有些地方只有 a 序列的某个位置与 b 序列的某个位置相等的时候才进行下一步。

现在在考虑怎么求题目中的要求的东西。

就是要求一个波浪形的序列。

满足 a[i - 1] > a[i] < a[i + 1] 或 a[i - 1] < a[i] > a[i + 1] 。

现在考虑怎么设计状态能只考虑现在这一步而且能转移到下一步。

那我们参考前面的求法。

添加一维,设 f[i][j][0/1] ,表示 a,b 序列分别以 i,j 结尾,并且公共子序列的前一位比现在这一位低或高。

所以假如现在 这一位 比 前一位 高,那 前一位的前一位 就比 前一位 高,或,这一位 比 前一位 低,那 前一位的前一位 就比 前一位 低。

这样子就可以只考虑当前这一位,不用考虑别的位置就可以转移了。

所以转移方程就出来了:

\[f[i][j][1] = max (f[k][l][0] + 1, f[i][j][1]);\\ f[i][j][0] = max (f[k][l][1] + 1, f[i][j][0]); \]

# include <bits/stdc++.h>
using namespace std;
int f[1007][1007][2];
int n, m;
int a[1007], b[1007];
int main () {
	cin >> n; for (int i = 1; i <= n; ++ i) cin >> a[i];
	cin >> m; for (int i = 1; i <= m; ++ i) cin >> b[i];
	int ans = 0;
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= m; ++ j) {
			if (a[i] != b[j]) continue;
			f[i][j][0] = f[i][j][1] = 1;
			if (ans < 1) ans = 1;
			for (int k = 1; k < i; ++ k) {
				if (a[i] == a[k]) continue;
				for (int l = 1; l < j; ++ l) {
					if (a[k] != b[l]) continue;
					if (a[k] > a[i]) {
						f[i][j][1] = max (f[k][l][0] + 1, f[i][j][1]);
						if (ans < f[i][j][1]) ans = f[i][j][1];
					}
					if (a[k] < a[i]) {
						f[i][j][0] = max (f[k][l][1] + 1, f[i][j][0]);
						if (ans < f[i][j][0]) ans = f[i][j][0];
					}
				}
			}
		}
	}
	cout << ans;
}

跟上面一样,因为几个 continue 的作用,复杂度非严格 \(O(n^2 m^2)\) ,所以跑过去 60 pts。

T3

考场上没想到能跑这些分,所以空间开小了。

只拿了 20pts,空间开大点的话还可以再多跑点分出来。

就是 O(n^2) 的建边,再跑 n 遍 dijstra 或 spfa ,然后据他们说 60 分就到手了……

其实我也能 60 的…

算一下复杂度,就是 \(O(n^3log~n^2)\) 不对劲。肯能是我算错了,也可能是数据水了……不懂,据我算的,只能过 \(n≤100\)

posted @ 2022-10-22 23:00  zcxxxxx  阅读(30)  评论(0编辑  收藏  举报