比赛-Round 1 (13 Jul, 2018)
1. 分队
每名选手都要属于一个队伍,考虑 A[i] 最大的选手是必须选的。考虑与他同一队的选手,显然尽量选 A[i] 大的其他选手,并且选的人数越少越好,因为选手越多,可划分的队伍数就越多。这样就得到了一个显然的贪心。这样做是错误的,据说可以过 80 分。1 2 4 5 5 5 5 5 这组数据在这样的贪心策略下无解,而实际上存在一个答案为 2 的解 (1, 2, 4) (5, 5, 5, 5, 5) 。所以其实并不是每个队选的人越少越好,因为有的选手的需求在其它队伍组合里无法满足,必须把他加入人数更多的队伍。因此考虑 DP ,在贪心的基础上(从小到大排序),f[i] 表示前 i 个选手最多可分队伍数。 f[i] = f[j] + 1 (0 <= j <= i-A[i]) ,前缀和优化一下就是 O(n) 的了。
1 #include <stdio.h> 2 #include <algorithm> 3 4 using namespace std; 5 6 int A[1200000], f[1200000], M[1200000]; 7 8 int main() 9 { 10 int N, i; 11 scanf("%d", &N); 12 for (i = 1; i <= N; ++i) 13 scanf("%d", &A[i]); 14 sort(A+1, A+1+N); 15 M[0] = 0; 16 for (i = 1; i <= N; ++i) { 17 if (i-A[i] >= 0) f[i] = M[i-A[i]]+1; 18 else f[i] = 0; 19 M[i] = max(M[i-1], f[i]); 20 } 21 printf("%d\n", f[N]); 22 return 0; 23 }
2. 子矩阵
枚举矩阵的 up, down, 考虑每一列可以往左和往右扩展多远。用单调栈维护。O(n3)。
1 #include <stdio.h> 2 #include <algorithm> 3 #include <string.h> 4 #include <stack> 5 6 using namespace std; 7 8 9 stack<int> S; 10 int A[320][320], Mn[320], R[320], ans[320*320]; 11 12 13 int main() 14 { 15 int i, j, up, dn, N, M; 16 scanf("%d%d", &N, &M); 17 for (i = 1; i <= N; ++i) 18 for (j = 1; j <= M; ++j) 19 scanf("%d", &A[i][j]); 20 for (up = 1; up <= N; ++up) 21 for (dn = up; dn <= N; ++dn) { 22 for (i = 1; i <= M; ++i) 23 if (up == dn) Mn[i] = A[dn][i]; 24 else Mn[i] = min(Mn[i], A[dn][i]); 25 memset(R, 0, sizeof R); 26 27 for (i = 1; i <= M; ++i) { 28 while (!S.empty() && Mn[S.top()] > Mn[i]) 29 R[S.top()] = i-S.top(), S.pop(); 30 S.push(i); 31 } 32 while (!S.empty()) R[S.top()] = M-S.top()+1, S.pop(); 33 34 for (i = M; i >= 1; --i) { 35 while (!S.empty() && Mn[S.top()] > Mn[i]) 36 ans[Mn[S.top()]] += R[S.top()]*(S.top()-i), S.pop(); 37 S.push(i); 38 } 39 while (!S.empty()) ans[Mn[S.top()]] += R[S.top()]*S.top(), S.pop(); 40 } 41 for (i = 1; i <= N*M; ++i) printf("%d\n", ans[i]); 42 return 0; 43 }
3. 数字对
对于 (mx, mn) (满足 mx > mn) ,它显然是且只能是由 (mx-mn, mn) 操作得到的。所以反向考虑问题枚举所有的 (n, i) ,计算它得到 (1, 1) 的步数,取最小值。这个过程类似欧几里得辗转相除,处理之后可以把单次计算步数优化到 O(logN) ,所以整体是 O(NlogN) 的。
不过玄学迭代加深搜索真的非常强。
搜索代码:
1 #include <cstdio> 2 3 int depth, N; 4 int Fi[53]; 5 bool flag; 6 7 void diu(int mx, int mn, int d) 8 { 9 if (d == depth) { 10 if (mx == N || mn == N) 11 flag = true; 12 return; 13 } 14 if (mx+(depth-d)*mn > N || Fi[depth-d+1]*mx+Fi[depth-d]*mn < N) return; 15 diu(mx+mn, mn, d+1); 16 if (flag) return; 17 diu(mx+mn, mx, d+1); 18 if (flag) return; 19 } 20 21 int main() 22 { 23 scanf("%d", &N); 24 Fi[1] = Fi[2] = 1; 25 for (depth = 0; depth <= 50; ++depth) { 26 if (depth > 1) Fi[depth+1] = Fi[depth]+Fi[depth-1]; 27 flag = false; 28 diu(1, 1, 0); 29 if (flag) break; 30 } 31 printf("%d\n", depth); 32 return 0; 33 }
我改悔罢!
1 题想到了贪心,手构样例没卡住自己,以为是正确的,因为不知道的原因超时拿了 60 分. 所以说贪心虽然机智,必须反复验证其正确性啊OrzOrz
2 题考虑过二维树状数组,然后考虑到对于第 i 答案,是比第 i-1 个多了一个限制条件的:不能选矩阵中第 i-1 个值。然后考虑这个值在矩阵的位置在当前第 i 个值在矩阵的位置的 左上,右下,…… 似乎就很像一个容斥原理,然而之后就推不动了。卡了整整一个半小时。而且由于 P3 做得比较快,时间还算充裕,先没有打暴力,一直纠结正解。最后时间紧打了发连样例都不能过的假容斥交了,爆 0。所以说不管能不能搞出正解,先写暴力呀,留着对拍也好。
3 题第一反应是搜索,迭代加深由于限制非常强,对拍了一下发现在题目范围内可以很快通过,所以没花太多时间就放下这道题了。考试结束之后实测了在 1e8 标程被卡死的情况下,迭代加深搜索仍然很快出答案,非常玄学,可能是因为这种迭代加深搜索的复杂度与 Fibonacci 数列有关吧。