七段第二课:贪心 A -【提高组】第二课:最小字典序 No.1 - 题目大意 每次可以从S的开头或者结尾取出一个字符,放到一个T字符串的尾部。输出T字典序最小的可能。
No.2 - 解题思路 如果直接从两头看那个小取肯定是不行的,样例也告诉我们:不可以乱取
1 2 3 4 5 6 7 8 9 10 11 12 S : "ACDBCB" T : "" S : "CDBCB" T : "A" S : "CDBC" T : "AB" S : "CDB" T : "ABC" S : "CD" T : "ABCB" S : "D" T : "ABCBC" S : "" T : "ABCBCD" S : "DBC" T : "ABC" S : "DB" T : "ABCC"
而这里,是什么决定了取左边还是右边呢?只是因为单纯取右边可以达到最优吗?显然不是,真正决定字典序的是不是那一个 B
。当方法2没有取到哪个 B
的时候,就已经奠定了比方法1大的结果了。
所以取字母只是表面,真正要做到的是:在保证字典序最小的情况下使得字典序小的字母 漏到两端,从而使得之后的答案更优。
因此,贪心时需要判断之后的字母大小,而题目的数据是 $2000$ ,$O(n^2)$ 不会超时,所以方向去写便是了。
No.3 - 关键代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 bool Check (int l, int r) { for ( ; l <= r; l++, r--) { if (s[l] < s[r]) { return 1 ; } else if (s[l] > s[r]) { return 0 ; } } return 1 ; } void Work () { int cnt = 0 ; for (int l = 1 , r = n; l <= r; ) { if (Check (l, r)) { printf ("%c" , s[l++]); cnt++; } else { printf ("%c" , s[r--]); cnt++; } if (cnt == 80 ) { printf ("\n" ); cnt=0 ; } } }
B -【提高组】第二课:雷达 No.1 - 题目大意 现在要在 $x$ 轴上放置若干雷达,每个雷达的辐射半径都是 $d$ 。 问至少需要几个雷达,才能够幅射所有点,如果辐射不到所有的点,则输出 $-1$ 。
No.2 - 解题思路 这道题一看是选择一些区间实现覆盖点的题目,但是显而易见如果这么写代码极其复杂而且不好理解。那就用转化的思想 ,可以不可以把点转化成区间然后找重合点呢?答案显然可以。
怎么实现呢?勾股定理。 已知高度,半径,就可以求出一个点最左哪里放雷达可以,最右哪里放雷达可以。然后就是区间覆盖问题了。
No.3 - 关键代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void Input () { scanf ("%d%d" , &n, &d); int x, y; for (int i = 1 ; i <= n; i++) { scanf ("%d%d" , &x, &y); if (abs (y) > d) { printf ("-1" ); return exit (0 ); } double sq = sqrt (d * d - y * y); a[i].L = x - sq; a[i].R = x + sq; } } void Work () { sort (a + 1 , a + n + 1 , [](Seg &a, Seg &b) { return a.R < b.R; }); double r1 = a[1 ].R; int cnt = 1 ; for (int i = 2 ; i <= n; i++) { if (a[i].L - r1 > eps) { cnt++; r1 = a[i].R; } } printf ("%d" ,cnt); }
C -【提高组】第二课:机器分配 No.1 - 题目大意 有 $N$ 个机器与 $M$ 个任务,每个机器或者任务都有两个属性 $(x,y)$ 。 机器的 $x$ 表示这个机器的最长工作时间,$y$ 表示机器的等级。 任务的 $x$ 表示完成这个任务需要花费的时间,$y$ 表示任务的等级。
每个机器只能做一个任务,机器的等级必须大于等于任务的等级。 每完成一个任务可以获得 $500\times x+2\times y$ 的价值( $x,y$ 是任务的 $x,y$ )。 请问最多可以完成多少个任务以及在此基础上最多可以获得多少的价值。
No.2 - 解题思路 这道题先观察给出的式子,发现:$x$ 在式子中占绝对优势,$x$ 的优先级一定是比 $y$ 高的。 所以,我们可以考虑按照 $x$ 排序后,再处理 $y$ 。
还有,这个式子是两个未知数越大才变大,所以应该优先处理价值大的任务才可以达到最大化的效果,因为如果先处理小的就会导致小的可能会抢走大的对应的机器。
最后,我们还发现,$y$ 的大小只有 $100$ 这意味着,我们可以使用数组统计 $y$ 应该处理那一个,这样就可以完美解决这一道题了。
No.3 - 关键代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void Work () { sort (a + 1 , a + n + 1 ); sort (b + 1 , b + m + 1 ); int now = 1 , ans = 0 ; long long tot = 0 ; for (int i = 1 ; i <= m; i++) { while (now <= n && a[now].a >= b[i].a) cnt[a[now++].b]++; for (int j=b[i].b; j<=100 ; j++){ if (cnt[j]>0 ){ cnt[j]--; ans++; tot+=b[i].a*500 +b[i].b*2 ; break ; } } } printf ("%d %lld" , ans, tot); }
D -【提高组】第二课:田忌赛马 No.1 - 题目大意 田忌赛马。速度快的赢200两银子,速度慢的输200两银子,速度一样则不赢也不输。 最大化赢得的银子数量。
No.2 - 解题思路 先比较两个人最强的马:能赢,就赢。如果一定赢不了,那么就拿最小的马打掉。 再比较两个人最弱的马:能赢,就赢。如果赢不了,那就输掉。如果两个最弱的马一样,再比较。 如果两个最弱的马一样:如果有马不同,就拿最小马打掉最大马。如果所有马都一样,那就平局。
以上策略是站在田忌角度说的(因为要最大化田忌的银子)。
No.3 - 关键代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 void Work () { sort (a + 1 , a + n + 1 ); sort (b + 1 , b + n + 1 ); long long ans = 0 ; int la = 1 , ra = n, lb = 1 , rb = n; while (la <= ra && lb <= rb) { if (a[ra] > b[rb]) { ans += 200 ; ra--, rb--; }else if (a[ra] < b[rb]) { ans -= 200 ; la++, rb--; } else { if (a[la] > b[lb]) { ans += 200 ; la++, lb++; }else if (a[la] < b[lb]) { ans -= 200 ; la++, rb--; } else { if (a[la] != a[ra]) { ans -= 200 ; } la++, rb--; } } } printf ("%lld" , ans);
E -【提高组】第二课:行列快乐值 No.1 - 题目大意 给定一 $n\times m$ 的矩阵,将对矩阵进行 $k$ 次操作。每次可以进行如下某一种操作:
将某行的元素相加作为快乐值,然后将该行元素都减去 $p$ 。
将某列的元素相加作为快乐值,然后将该列元素都减去 $p$ 。
问k次之后快乐值总和的最大值是多少?
No.2 - 解题思路 这道题看上去策略很明显,那就是大的优先取 ,但是还有一个要慎重考虑的是:某一列所有值减去 $p$ 以后,所有行的值都得减去 $p$, 而这个很难考虑到如何动态去完成功能。
脑子转过来,我们发现,其实先减行还是先减列都是差不多的。给出如下画图证明:
而两者,整理后的结果都是:$b+h+d+f+e\times2-p$
也就是说,我们完全可以先考虑减列,再考虑减行。
定义一个 $\tt sum$ 数组表示前 $k$ 次取列 ,后几次取行的价值,然后模拟即可,另外不要忘记减去修改列所影响行的价值。
No.3 - 关键代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void Work () { Init (); long long toth = 0 , totl = 0 ; for (int i = 1 ; i <= k; i++) { int hang = qhang.top (); qhang.pop (); qhang.push (hang - m * p); toth += hang; sum[i] += toth; int lie = qlie.top (); qlie.pop (); qlie.push (lie - n * p); totl += lie; sum[k-i] += totl; if (2 * i <= k) { sum[i] -= 1LL * i * (k - i) * p; if (i != k-i) { sum[k-i] -= 1LL * i * (k - i) * p; } } } long long ans = -(1LL << 60 ); for (int i = 0 ; i <= k; i++) { ans=max (ans, sum[i]); } printf ("%lld" , ans); }
F- 第二课:取数求和 No.1 - 题目大意 给定 $m$ 个长度为 $n$ 的数组,要求从每一个数组中取出一个数字然后相加,这样的取法总共有 $n^m$ 种。 问相加之后和最小的 $n$ 种是多少。
No.2 - 解题思路 显而易见的,把所有数字从小到大排序后,一定是从最小的开始递推才可以得到最优解,然后记得哈希去重。
No.3 - 关键代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 node p; for (int i=1 ; i<=m; i++){ p.v.push_back (1 ); p.sum+=a[i][1 ]; } mp[hash_ (p.v)]=1 ; q.push (p); int gMr=n;while (gMr--){ node now=q.top (); q.pop (); printf ("%d " , now.sum); for (int i=1 ; i<=m; i++){ node nxt=now; if (nxt.v[i-1 ]==n) continue ; nxt.sum-=a[i][nxt.v[i-1 ]]; nxt.v[i-1 ]++; nxt.sum+=a[i][nxt.v[i-1 ]]; int h=hash_ (nxt.v); if (!mp[h]){ q.push (nxt); mp[h]=1 ; } } }