比赛-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
", 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
", 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
", depth);
32     return 0;
33 }

我改悔罢!
1 题想到了贪心,手构样例没卡住自己,以为是正确的,因为不知道的原因超时拿了 60 分. 所以说贪心虽然机智,必须反复验证其正确性啊OrzOrz
2 题考虑过二维树状数组,然后考虑到对于第 i 答案,是比第 i-1 个多了一个限制条件的:不能选矩阵中第 i-1 个值。然后考虑这个值在矩阵的位置在当前第 i 个值在矩阵的位置的 左上,右下,…… 似乎就很像一个容斥原理,然而之后就推不动了。卡了整整一个半小时。而且由于 P3 做得比较快,时间还算充裕,先没有打暴力,一直纠结正解。最后时间紧打了发连样例都不能过的假容斥交了,爆 0。所以说不管能不能搞出正解,先写暴力呀,留着对拍也好。

3 题第一反应是搜索,迭代加深由于限制非常强,对拍了一下发现在题目范围内可以很快通过,所以没花太多时间就放下这道题了。考试结束之后实测了在 1e8 标程被卡死的情况下,迭代加深搜索仍然很快出答案,非常玄学,可能是因为这种迭代加深搜索的复杂度与 Fibonacci 数列有关吧。

原文地址:https://www.cnblogs.com/ghcred/p/9305411.html