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 :
有这样一个式子:
就是说如果(其他人的工作时间)大于这个最大的(工作时间+休息时间)的话。
那所有其他人的(工作时间+休息时间)一定也小于这个(其他人的工作时间)。
所以我们可以枚举最大的(工作时间+休息时间),然后选择的其他人要满足(工作时间+休息时间)小于这个最大值。
因为我们要让上面那个式子尽可能成立,也就是在满足选完了最大值之后,其他人要选择 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 结尾,并且公共子序列的前一位比现在这一位低或高。
所以假如现在 这一位 比 前一位 高,那 前一位的前一位 就比 前一位 高,或,这一位 比 前一位 低,那 前一位的前一位 就比 前一位 低。
这样子就可以只考虑当前这一位,不用考虑别的位置就可以转移了。
所以转移方程就出来了:
# 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\) 。