题解:P9468 [EGOI 2023] Candy / 糖果

P9468 [EGOI 2023] Candy / 糖果

题目描述


在伊卡古城,据说有一座宫殿,其财富超乎想象。在其中,一个走廊中有 N N N 盒来自世界各地的糖果。路过的旅行者只要用黄金支付糖果的重量,就可以拿走任意多的糖果。

装糖果的盒子被从左到右编号为 0 0 0 N − 1 N-1 N1。在盒子 i i i 中,剩余有 a i a_i ai 单位的糖果,其中 a i a_i ai 是一个非负整数。

作为宫殿的守护者,你需要移动这些盒子,使得有很多糖果的盒子更靠近入口。

你已知数组 a 0 , a 1 , ⋯   , a N − 1 a_0,a_1,\cdots,a_{N-1} a0,a1,,aN1,以及整数 F F F T T T。在一次操作中,你被允许交换 a 0 , a 1 , ⋯   , a N − 1 a_0,a_1,\cdots,a_{N-1} a0,a1,,aN1 中的任意两个相邻元素。要使得数组前 F F F 个元素的和至少为 T T T,至少需要多少次操作?

输入格式


第一行三个整数 N , F , T N,F,T N,F,T

\第二行 N N N 个整数 a 0 , a 1 , ⋯   , a N − 1 a_0,a_1,\cdots,a_{N-1} a0,a1,,aN1
\

输出格式


如果不可能达成目标,输出 NO

否则,输出一个整数,表示最少操作数。
\

输入 #1

6 2 27
10 4 20 6 3 3

输出 #1

1

输入 #2

6 5 5000000000
1000000000 1000000000 0 1000000000 1000000000 1000000000

输出 #2

3

输入 #3

3 2 100
20 30 60

输出 #3

NO

输入 #4

1 1 100
100

输出 #4

0

说明/提示


样例 1 1 1 解释

在样例 1 1 1 中,前两个元素的和应当至少为 27 27 27。一次相邻元素的交换就可以达成目标:交换 4 4 4 20 20 20。在这次交换后,数组变成 10 20 4 6 3 3,前两个元素的和为 10 + 20 = 30 ≥ 27 10+20=30\ge 27 10+20=3027



样例 2 2 2 解释

在样例 2 2 2 中,这个 0 0 0 必须一路移动到数组末尾;这需要 3 3 3 次交换。


样例 3 3 3 解释

在样例 3 3 3 中,不可能使得前两个元素和至少为 100 100 100;我们能做到的最好结果是 60 + 30 = 90 60+30=90 60+30=90


数据范围

对于全部数据, 1 ≤ N ≤ 100 1\le N\le 100 1N100 1 ≤ F ≤ N 1\le F\le N 1FN 0 ≤ T ≤ 1 0 11 0\le T\le 10^{11} 0T1011 0 ≤ a i ≤ 1 0 9 0\le a_i\le 10^9 0ai109
\

  • 子任务一( 6 6 6 分): N ≤ 2 N\le 2 N2 a i ≤ 100 a_i\le 100 ai100 T ≤ 1 0 9 T\le 10^9 T109
  • 子任务二( 19 19 19 分): a i ≤ 1 a_i\le 1 ai1
  • 子任务三( 16 16 16 分): N ≤ 20 N\le 20 N20
  • 子任务四( 30 30 30 分): a i ≤ 100 a_i\le 100 ai100
  • 子任务五( 29 29 29 分):无特殊限制。

提示

答案可能不在 32 32 32 位整型范围内,如果你使用 C++ 语言,请注意溢出的可能。

有一个很妙的思路,分享一下。

根据题目的意图,我们要在移动距离最短前提下,使得数组的前 F F F 个元素的和大于等于 T T T。显然只能从后往前移动,不然无意义。

设选中的 F F F 个元素在原数组中的位置为 x 1 ≤ x 2 ≤ ⋯ ≤ x F x_1 \leq x_2 \leq \dots \leq x_F x1x2xF,经过交换后在前 F F F 个位置的最终位置为 y 1 < y 2 < ⋯ < y F y_1 < y_2 < \dots < y_F y1<y2<<yF(其中 y i ∈ [ 1 , F ] y_i \in [1,F] yi[1,F])。

将元素从 x i x_i xi 移动到 y i y_i yi 需要 ∣ x i − y i ∣ |x_i - y_i| xiyi 次相邻交换,总交换次数即为 ∑ i = 1 F ( x i − y i ) \sum\limits_{i=1}^F (x_i - y_i) i=1F(xiyi),因为 x i ≥ y i x_i \geq y_i xiyi

显然不管怎么交换,原来位置和 ∑ x i \sum x_i xi 一定等于交换后的位置和 ∑ y i \sum y_i yi 加上交换时移动的距离(就是交换次数)

设交换次数为 t t t,则 $ \sum x_i = \sum y_i + t$,移项可得 t = ∑ x i − ∑ y i t = \sum x_i - \sum y_i t=xiyi

可得对于前 F F F 个位置, y i y_i yi 固定取 1 , 2 , 3 , … F 1,2,3,\dots F 1,2,3,F,位置和等于 F ( F + 1 ) 2 \frac{F(F+1)}{2} 2F(F+1)

所以,最小交换次数的计算方法如下:

t = ∑ x i − F ( F + 1 ) 2 t=\sum x_i - \frac{F(F+1)}{2} t=xi2F(F+1)

要使得交换次数 t t t 最小,就要使得 ∑ x i \sum x_i xi 尽可能小。


所以,我们要选中 F F F 个数值总和大于等于 T T T 的数,在数值总和都大于等于 T T T 时就让位置和最小。

不难想到这一步可以用 dp 解决。设 d p i , j , k dp_{i,j,k} dpi,j,k 表示当前在位置 i i i,选了 j j j 个元素,选中的所有元素下标和为 k k k 时的最大数值总和。

转移方程比较简单,只要对于每个不同的元素 i i i,尝试加入之前不同的状态就行了:

d p i , j , k = max ⁡ { d p i − 1 , j − 1 , k − i + a i }   ( j ≤ min ⁡ { i , F } ) dp_{i,j,k} = \max\{ dp_{i-1,j-1,k-i} + a_i \}\ (j \le \min\{i,F\}) dpi,j,k=max{dpi1,j1,ki+ai} (jmin{i,F})

转移时可以滚动数组,不过要倒序枚举 j j j

时间复杂度约为 O ( 5000 × n F ) O(5000 \times nF) O(5000×nF) 100 ( 100 + 1 ) 2 = 5050 \frac{100(100+1)}{2}=5050 2100(100+1)=5050)。

#include 
using namespace std;
typedef long long ll;

ll N, F, T;
ll a[105], dp[105][5100]; // dp[j][k]:(当前第i个数)选了j个,下标和为k时,数值和的最大值

void solve()
{
    cin >> N >> F >> T;
    for(int i = 1; i <= N; i ++){
        cin >> a[i];
    }

    memset(dp, -0x3f, sizeof dp);
    dp[0][0] = 0;
    for(int i = 1; i <= N; i ++){
        for(int j = min(1LL*i, F); j >= 1; j --){
            for(int k = i; k <= 5050; k ++){ // 100*(100+1)/2 = 5050
                dp[j][k] = max(dp[j][k], dp[j - 1][k - i] + a[i]);
            }
        }
    }

    int minp = 1e9;
    for(int k = 0; k <= 5050; k ++){
        if(dp[F][k] >= T) minp = min(minp, k);
    }
    if(minp == 1e9) puts("NO"); else cout << max(0LL, 1LL*minp - F*(F+1)/2) << '\n';
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    solve();
    return 0;
}

你可能感兴趣的:(刷题之路,C++算法技巧,算法,动态规划,数据结构,c++,数学建模,矩阵,笔记)