Codeforces Round #831 (Div. 1 + Div. 2)题解 A~E
Codeforces Round #831 (Div. 1 + Div. 2)
A. Factorise N+M
题意
给一个质数 n n n。求一个质数 m m m,使得 n + m n+m n+m 不是质数。
分析
利用奇偶性即可。若 n = 2 n=2 n=2,令 m = 3 m=3 m=3;否则 ,令 m = 2 m=2 m=2。
当然,也可以直接输出 m = 7 m=7 m=7。
代码(python)
a = int(input())
for i in range(a):
input()
print(7)
B. Jumbo Extra Cheese 2
题意
有 n n n 个矩形,每个大小为 a i × b i a_i\times b_i ai×bi。
要把他们全都放在 x x x 轴上,每个矩形都要贴着 x x x 轴放。(可以打乱顺序,可以旋转,不能重叠)。求最后形成的组合图形的周长最小值。
分析
正确的策略是:
把所有的矩形都竖着放,即较短的边贴着 x x x 轴。
组成图形的长就是 所有矩形最短的边相加,即 ∑ i = 1 n min ( a i , b i ) \sum_{i=1}^n\min(a_i,b_i) ∑i=1nmin(ai,bi) ,高就是所有矩形最长的边的最大值,即 max 1 ≤ i ≤ n ( a i , b i ) \max_{1\le i\le n}{(a_i,b_i)} max1≤i≤n(ai,bi)。总答案就是 ( 长 + 高 ) × 2 (长+高)\times 2 (长+高)×2
证明:
记最高的矩形为 t ( a t ≤ b t ) t(a_t\le b_t) t(at≤bt),只有 t t t 决定了图形的高,为 b t b_t bt。
假如把除了 t t t 之外的矩形放倒,那么不会影响图形的高,却增加了图形的长,必然不可。
假如把 t t t 放倒,那么 “新图形的高” ≥ a t \ge a_t ≥at,即 “高减小的长度” ≤ b t − a t \le b_t-a_t ≤bt−at,而"长增加的长度" = b t − a t =b_t-a_t =bt−at ,所以一定不可能让结果变优。
代码(python)
for CASE in range(int(input())):
w,h = 0, 0
for i in range (int(input())):
a,b=map(int,input().split())
w += min(a, b)
h = max(h, a, b)
print((w+h)*2)
C. Bricks and Bags
题意
有 n n n 个数字 a 1 . . . a n a_1...a_n a1...an,Brick 把这些数字放到 3 3 3 个背包中,每个背包至少有一个数字。Bu 从这三个背包中各取出一个数字,从第 i i i 个背包中取出的记为 w i w_i wi,记得分为 ∣ w 1 − w 2 ∣ + ∣ w 2 − w 3 ∣ |w_1 - w_2| + |w_2 - w_3| ∣w1−w2∣+∣w2−w3∣。
Bu 希望得分尽可能小,求 Brick 通过调整数字放置方案所得到的得分最大值。
分析
先说一下比赛中我一开始想到的错误的贪心策略(这个题卡了很多很多人,好像都是因为这样错的):
把最大的数字和最小的数字分别放到两个包裹(有重复的就讨论序列中总共出现了几个不同的数字,这里不展开了),再讨论这两个包裹哪一个作为 2 2 2 号包裹。其他的数字全部放到 3 3 3 号包裹。
这个策略会被这一组数据卡掉:
1 6 14 15
按上述策略构造是:(1), (15), (6, 14)。Bu 取出 1, 15, 14,答案为15。
而实际上的最优策略是:(1), (14, 15), (6)。Bu 取出1, 14, 6,答案为 21。
下面讨论正确的做法。其实差别不是很大。
性质 1 1 1:
2 2 2 号包裹中的数字要么大于 1 , 3 1, 3 1,3 号包裹中的所有数字,要么全小于。即 2 2 2 号包裹中不能出现某个数字 x 2 x_2 x2 大于 1 1 1 号包裹的某个数字 x 1 x_1 x1 ,却小于 3 3 3 号包裹的某个数字 x 3 x_3 x3 。
证明:
如果出现这种情况,Bu可以直接取 x 1 , x 2 , x 3 x_1,x_2,x_3 x1,x2,x3 作为 w 1 , w 2 , w 3 w_1,w_2,w_3 w1,w2,w3,所得的答案为 ∣ x 1 − x 2 ∣ + ∣ x 2 − x 3 ∣ = ∣ x 3 − x 1 ∣ |x_1-x_2|+|x_2-x_3|=|x_3-x_1| ∣x1−x2∣+∣x2−x3∣=∣x3−x1∣。
而如果把所有大于等于 x 3 x_3 x3 的数字放在 2 2 2 号包裹中,所有小于等于 x 1 x_1 x1 的数字放到 1 1 1 号包裹中,其余数字放到 $3 $ 号包裹中,仅从 1 , 2 1,2 1,2 两个包裹中取出的数字作差就已经不小于 ∣ x 3 − x 1 ∣ |x_3-x_1| ∣x3−x1∣,再从 3 3 3 号包裹中取数字,所得答案一定大于 ∣ x 3 − x 1 ∣ |x_3-x_1| ∣x3−x1∣。
从性质 1 1 1 中我们直到,应该先把数组排序,然后有两种选择。要么把最大的连续的某些数字放到包裹 2 2 2 中,这样Bu 会选择这些数字中最小的作为 w 2 w_2 w2;要么把最小的连续的某些数字放到包裹 2 2 2 中,这样 Bu 会选择这些数字中最大的作为 w 2 w_2 w2 。
分别讨论这两种策略,再枚举包裹 2 2 2 划分的边界即可。我们以第一种策略为例,在枚举的过程中假设 w 2 w_2 w2 已经确定了。
小于 w 1 w_1 w1 的最大的数字记为 t t t,则一定在包裹 1 1 1 或 3 3 3 中,假设把它放到包裹 1 1 1。那么Bu 一定会选择这个数字作为 w 1 w_1 w1,因为这样可以在不影响 ∣ w 2 − w 3 ∣ |w_2-w_3| ∣w2−w3∣ 的情况下最小化 ∣ w 2 − w 1 ∣ |w_2-w_1| ∣w2−w1∣。这样 w 1 , w 2 w_1,w_2 w1,w2 都已经确定了。
最后一步,只需要把整个数组中最小的数字放到包裹 3 3 3 中即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF 1e18
/******************************************************************/
/******************************************************************/
/**************************以下是做题代码***************************/
int a[300000], n;
void SolveTest() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + 1 + n);
int ans = 0;
//策略1
for (int i = n; i >= 3; i--) {
ans = max(ans, a[i] - a[1] + a[i] - a[i - 1]);
}
//策略2
for (int i = 1; i <= n - 2; i++) {
ans = max(ans, a[i + 1] - a[i] + a[n] - a[i]);
}
cout << ans << endl;
}
/**************************以上是做题代码***************************/
/******************************************************************/
/******************************************************************/
signed main() {
#ifdef CQLOCAL
freopen("C:/Users/20824/Desktop/C++代码/in.txt", "r", stdin);
freopen("C:/Users/20824/Desktop/C++代码/out.txt", "w", stdout);
#endif
int TestCase = 1;
cin >> TestCase;
for (int i = 1; i <= TestCase; i++) {
SolveTest();
}
return 0;
}
D. Knowledge Cards
题意
给一个 n × m n\times m n×m 的网格。有 k k k 张卡片,每张有一个数字,是 1... k 1...k 1...k 的全排列。
初始时,他们堆叠在 ( 1 , 1 ) (1,1) (1,1) 处,从上到下的顺序是序列 a 1 . . . a k a_1...a_k a1...ak 。
每一次操作有如下规则:
- 把某张卡片挪到上下左右相邻的位置;
- 除了 ( 1 , 1 ) , ( n , m ) (1,1), (n,m) (1,1),(n,m) 两个格子,其他每个格子上面最多有一张卡片;
- 某张卡片挪到 ( n , m ) (n,m) (n,m) 并且上面已经有一些卡片,就把这张卡片堆叠在 ( n , m ) (n,m) (n,m) 的所有卡片的最上方;
- 不允许把卡片移进 ( 1 , 1 ) (1,1) (1,1) ,也不许把卡片移出 ( n , m ) (n,m) (n,m);
需要把所有卡片移到 ( n , m ) (n,m) (n,m) ,并且从上到下形成 1... k 1...k 1...k 的顺序。问能否做到。
分析
结论:
只要整个网格中,除了 ( 1 , 1 ) (1,1) (1,1) 和 ( n , m ) (n,m) (n,m) 之外还有两个空格子,就可以让 ( 1 , 1 ) (1,1) (1,1) 的最上面的卡片直接挪到 ( n , m ) (n,m) (n,m)。
把 ( 1 , 1 ) (1,1) (1,1) 最上面的卡片拿下来需要安排一个空格子,然后还需要一个空格子来进行辗转腾挪。所以最大的空格子库存空间为 n × m − 4 n\times m-4 n×m−4。
ps:一开始做的时候误以为最大的库存空间是 ( n − 1 ) × ( m − 1 ) (n-1)\times (m-1) (n−1)×(m−1),即恰好留出一条过道直通 ( 1 , 1 ) (1,1) (1,1) 和 ( n , m ) (n,m) (n,m)。而且样例数据也太水发现不了错误。
其实是一个数字华容道问题的简化,一个很经典的数字游戏。
之后的问题就很简单了,简单模拟一下从 k k k 到 1 1 1 的每个数字能否到达 ( n , m ) (n,m) (n,m) 。假定数字 $ num$ 在 $ a$ 中的位置为 p n u m p_{num} pnum ,目前已经挪到 ( n , m ) (n,m) (n,m) 的最大 p i p_i pi 值为 x x x(初始化为 0 0 0 )。用 t m p tmp tmp 记录当前空格子库存的容量,初始为 n m − 4 nm-4 nm−4。从 k k k 到 1 1 1 枚举 n u m num num,判断空格子库存是否不足。具体逻辑见代码。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
/******************************************************************/
/******************************************************************/
/**************************以下是做题代码***************************/
const int N = 2e5;
int n, m, k;
int a[N], p[N];
#define YES puts("YA");return;
#define NO puts("TIDAK");return;
void SolveTest() {
cin >> n >> m >> k;
for (int i = 1; i <= k; i++) {
scanf("%lld", &a[i]);
p[a[i]] = i;
}
int tmp = n * m - 4, x = 0;
for (int i = k; i >= 1; i--) {
if (x > p[i]) {
tmp++;
} else {
tmp -= p[i] - 1 - x;
if (tmp < 0) {
NO;
}
x = p[i];
}
}
YES;
}
/**************************以上是做题代码***************************/
/******************************************************************/
/******************************************************************/
signed main() {
#ifdef CQLOCAL
freopen("C:/Users/20824/Desktop/C++代码/in.txt", "r", stdin);
freopen("C:/Users/20824/Desktop/C++代码/out.txt", "w", stdout);
#endif
int TestCase = 1;
cin >> TestCase;
for (int i = 1; i <= TestCase; i++) {
SolveTest();
}
return 0;
}
E. Hanging Hearts
题意
给一棵树。进行 n n n 轮操作,每轮操作规则如下:
- 选择一个叶子节点 x x x 摘掉;
- 如果 x x x 的点权小于其父节点的点权,就把它父节点的点权修改为 x x x 的点权;
最终得分为:摘掉叶子的点权从新到旧组成一个序列,该序列的最大不下降子序列长度。
问题是,需要给每个赋点权,所有的点权是 1... n 1...n 1...n 的一个排列,使得最终得分最大。
分析
用 f u f_u fu 记录以 u u u 为根的子树全部摘掉后,所得到序列的最大得分。
f [ u ] [ 0 ] f[u][0] f[u][0] 表示以 u u u 为结尾的最大不降子序列长度, f [ u ] [ 1 ] f[u][1] f[u][1] 表示不 u u u 为结尾的最大不降子序列长度。
首先看 f [ u ] [ 1 ] f[u][1] f[u][1] 怎样更新。
结论是 f [ u ] [ 1 ] f[u][1] f[u][1] 一定是一个从叶子节点开始到 u u u 的连续的长度。
证明:首先一个比较显然的贪心策略是,应该在越深的节点安排越小的点权,在根节点安排比较大的点权。根据删除操作的规则,一个节点被删掉的时候,他的点权会被修改为以他为根的子树的点权最小值。所以 u u u 被删除之前,各个子节点的点权都是各个子树点权最小值,且互不相同(注意题干中的“排列”)。所以 u u u 只能选择所有子节点的其中之一并把点权修改为它,而这条链也就成为了这一棵子树的删除序列的最长不上升子序列。
因此我们在转移的时候,把 f [ u ] [ 1 ] f[u][1] f[u][1] 完全等价为 u u u 到叶子节点的最大距离深度即可,即 f [ u ] [ 1 ] = 1 + max v ∈ s o n ( u ) f [ v ] [ 1 ] f[u][1]=1+\max_{v\in son(u)}{f[v][1]} f[u][1]=1+maxv∈son(u)f[v][1]。
然后看 f [ u ] [ 0 ] f[u][0] f[u][0] 怎样更新。
u u u 的所有子节点的子树可以“并行”删除,详细地说, u u u 的每一个子节点为根的子树在删除的过程中都产生格子序列,它们都有一个最大不降子序列。而由于可以随时切换摘除这些子树的进程,我们可以把这些子序列组合成一个大的序列,长度是这些子序列的和。即 f [ u ] [ 0 ] = ∑ v ∈ s o n ( u ) max ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) f[u][0]=\sum_{v\in son(u)}\max(f[v][0],f[v][1]) f[u][0]=∑v∈son(u)max(f[v][0],f[v][1])。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF 1e18
/******************************************************************/
/******************************************************************/
/**************************以下是做题代码***************************/
const int N = 2e6;
int f[N][2];
int idx = 0, to[N], nxt[N], head[N];
int n;
void add(int u, int v) {
to[++idx] = v;
nxt[idx] = head[u];
head[u] = idx;
}
void dfs(int u) {
f[u][1] = 1;
for (int i = head[u]; i; i = nxt[i]) {
int v = to[i];
dfs(v);
f[u][1] = max(f[u][1], f[v][1] + 1);
f[u][0] += max(f[v][0], f[v][1]);
}
}
void SolveTest() {
cin >> n;
for (int i = 2, x; i <= n; i++) {
scanf("%lld", &x);
add(x, i);
}
dfs(1);
printf("%lld", max(f[1][0], f[1][1]));
}
/**************************以上是做题代码***************************/
/******************************************************************/
/******************************************************************/
signed main() {
#ifdef CQLOCAL
freopen("C:/Users/20824/Desktop/C++代码/in.txt", "r", stdin);
freopen("C:/Users/20824/Desktop/C++代码/out.txt", "w", stdout);
#endif
int TestCase = 1;
// cin >> TestCase;
for (int i = 1; i <= TestCase; i++) {
SolveTest();
}
return 0;
}