Codeforces Round #831 (Div. 1 + Div. 2) A~E

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)} max1in(ai,bi)。总答案就是 ( 长 + 高 ) × 2 (长+高)\times 2 (+)×2

证明:

记最高的矩形为 t ( a t ≤ b t ) t(a_t\le b_t) t(atbt),只有 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 btat,而"长增加的长度" = b t − a t =b_t-a_t =btat ,所以一定不可能让结果变优。

代码(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| w1w2+w2w3

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| x1x2+x2x3=x3x1

而如果把所有大于等于 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| x3x1,再从 3 3 3 号包裹中取数字,所得答案一定大于 ∣ x 3 − x 1 ∣ |x_3-x_1| x3x1

从性质 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| w2w3 的情况下最小化 ∣ w 2 − w 1 ∣ |w_2-w_1| w2w1。这样 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×m4

ps:一开始做的时候误以为最大的库存空间是 ( n − 1 ) × ( m − 1 ) (n-1)\times (m-1) (n1)×(m1),即恰好留出一条过道直通 ( 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 nm4。从 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+maxvson(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]=vson(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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值