【牛客小白月赛27:贪心 | DP |(详解)】B:乐团派对

牛客小白月赛27:B题 乐团派对

【难度】

3 / 10 3/10 3/10
鄙人不才,WA了8发。。

【题意】

你有 n n n 个人,每个人有能力值 a i a_i ai ,表示该人所在的队伍人数必须大于等于 a i a_i ai

保证每个人都被分进一个队的情况下,队伍数量最多是多少?无解输出 − 1 -1 1

【数据范围】

0 ≤ n ≤ 1 0 5 0\le n\le 10^5 0n105
1 ≤ a i ≤ 1 0 9 1\le a_i\le 10^9 1ai109

【样例输入】

n n n
a 1 , a 2 , ⋯   , a n a_1 ,a_2,\cdots,a_n a1,a2,,an

4
2 1 2 1

【样例输出】

3

【解释】

队伍1:第一个人、第三个人
队伍2:第二个人
队伍3:第三个人

【思路】

(一)首先考虑贪心

首先判断可行性。易得,若 max ⁡ { a i } > n \max\{a_i\}>n max{ai}>n 则无解,输出 − 1 -1 1
接下来,人怎么分组?
既然是贪心,易想到我们需要对能力值进行排序。
首 先 为 了 防 止 歧 义 , 这 里 先 对 能 力 数 组 进 行 升 序 处 理 \color{green}首先为了防止歧义,这里先对能力数组进行升序处理

(1)逆序贪心?

【做法】
我们把 i i i n n n 1 1 1 进行循环遍历。
若对于第 i i i 个人能力值为 a i a_i ai 是接下里还没分好组的人。
接下来,我们选择 i − a [ i ] + 1 ∼ i i-a[i]+1\sim i ia[i]+1i ,把这些人组成一支队伍。
i − a [ i ] + 1 ≥ 1 i-a[i]+1\ge1 ia[i]+11,则可以划分队伍,因为易得 ∀ k ∈ [ i − a [ i ] + 1 , i ] , a [ k ] ≤ a [ i ] \forall k\in[i-a[i]+1,i],a[k]\le a[i] k[ia[i]+1,i]a[k]a[i]
i − a [ i ] + 1 < 1 i-a[i]+1<1 ia[i]+1<1,则无法划分队伍,就把他们合并到人数最多的队伍。

【结果】
可以AC

【 但 是 】 \color{cyan}【但是】
有反例可以 H a c k \color{red}Hack Hack
比如 n = 12 , a [ ] = { 6 , 6 , 6 , 6 , 6 , 6 , 6 , 1 , 1 , 1 , 1 , 1 } n=12,a[]=\{6,6,6,6,6,6,6,1,1,1,1,1\} n=12,a[]={6,6,6,6,6,6,6,1,1,1,1,1}
按照该贪心算法,分组为 { 6 , 6 , 6 , 6 , 6 , 6    ∣    6 , 1 , 1 , 1 , 1 , 1 } \{6,6,6,6,6,6\;\Big|\;6,1,1,1,1,1\} {6,6,6,6,6,66,1,1,1,1,1},为两组。
但是正确的分组应该为 { 6 , 6 , 6 , 6 , 6 , 6 , 6    ∣    1    ∣    1    ∣    1    ∣    1    ∣    1 } \{6,6,6,6,6,6,6\;\Big|\;1\;\Big|\;1\;\Big|\;1\;\Big|\;1\;\Big|\;1\} {6,6,6,6,6,6,611111},为六组。
牛客数据水了

【结论】
该 算 法 并 不 正 确 。 \color{green}该算法并不正确。

(2)顺序贪心?

【做法】
我们把 i i i 1 1 1 n n n 进行循环遍历。
若对于第 i i i 个人能力值为 a i a_i ai 是接下里还没分好组的人。
接下来,我们选择 i ∼ i + a [ i ] − 1 i\sim i+a[i]-1 ii+a[i]1 ,把这些人组成一支队伍。
i + a [ i ] − 1 > n i+a[i]-1>n i+a[i]1>n,则无法划分队伍,就把他们合并到人数最多的队伍。

【结果】
92.86 分 92.86分 92.86

【 但 是 】 \color{cyan}【但是】
该算法 非 常 错 误 \color{red}非常错误
∀ k ∈ [ i , i + a [ i ] − 1 ] , a [ k ] ≤ a [ i ] \forall k\in[i,i+a[i]-1],a[k]\le a[i] k[i,i+a[i]1]a[k]a[i] 当然不一定成立。
比如分组 { 2 , 3 , 3 , 4 , 5 } \{2,3,3,4,5\} {2,3,3,4,5}
按该算法的分组为 { 2 , 3    ∣    3 , 4 , 5 } \{2,3\;\Big|\;3,4,5\} {2,33,4,5},为两组。但是分组不满足题目要求呀!
但是正确的分组应该为 { 2 , 3 , 3 , 4 , 5 } \{2,3,3,4,5\} {2,3,3,4,5},为一组。

【结论】
该 算 法 并 不 正 确 。 \color{green}该算法并不正确。

(3)改良顺序贪心?

【做法】
我们把 i i i 1 1 1 n n n 进行循环遍历。
若对于第 i i i 个人能力值为 a i a_i ai 是接下里还没分好组的人。
接下来,我们选择 i ∼ i + a [ i ] − 1 i\sim i+a[i]-1 ii+a[i]1 ,把这些人组成一支队伍。
a [ i + a [ i ] − 1 ] > a [ i ] a[i+a[i]-1]>a[i] a[i+a[i]1]>a[i],我们继续选择 a [ i + a [ i ] − 1 ] − a [ i ] 个 人 a[i+a[i]-1]-a[i]个人 a[i+a[i]1]a[i]
直到第一个合法的 k k k 满足 max ⁡ k p = i { a p } = a [ k ] > k − i + 1 \underset{p=i}{\overset{k}{\max}}\{a_p\}=a[k]>k-i+1 p=imaxk{ap}=a[k]>ki+1
若 找不到合法的 k k k ,则这些人与最大人数的队伍合并。

【 但 是 】 \color{cyan}【但是】
该算法 有 点 问 题 \color{red}有点问题
比如分组 { 1 , 1 , 2 , 2 , 5 } \{1,1,2,2,5\} {1,1,2,2,5}
按该算法, i i i 枚举到 4 4 4时,目前分组为 { 1    ∣    1    ∣    2 , 2    } \{1\;\Big|\;1\;\Big|\;2,2\;\} {112,2}
但是遇到 5 5 5 ,我无法选出5个人来,也无法在某个已有团队中直接把这个人给塞进去。
我们还要再计算最少数目的已有团队,使得团队总人数 s ≥ a i − 1 s\ge a_i-1 sai1
怎么变成背包问题了??这可是贪心做法!

【结论】
该 算 法 并 不 优 秀 。 \color{green}该算法并不优秀。

(4)正解贪心

【做法】

升序排序后,我们先把 a n a_n an 安排掉,易得至少要 a n a_n an个人。
我们安排 [ n − a [ n ] + 1 , n ] \pmb{[n-a[n]+1,n]} [na[n]+1,n][na[n]+1,n][na[n]+1,n] 这些下标的人:
∀ k ∈ [ n − a [ n ] + 1 , n ] , a [ k ] ≤ a [ n ] \forall k\in[n-a[n]+1,n],a[k]\le a[n] k[na[n]+1,n]a[k]a[n] 成立,所以该选择一定是合法的、最贪心的。

接下来,我们把 i i i 1 1 1 n − a [ n ] n-a[n] na[n] 进行循环遍历。与做法3的贪心选择相同:

若对于第 i i i 个人能力值为 a i a_i ai 是接下里还没分好组的人。
接下来,我们选择 i ∼ i + a [ i ] − 1 i\sim i+a[i]-1 ii+a[i]1 ,把这些人组成一支队伍。
a [ i + a [ i ] − 1 ] > a [ i ] a[i+a[i]-1]>a[i] a[i+a[i]1]>a[i],我们继续选择 a [ i + a [ i ] − 1 ] − a [ i ] 个 人 a[i+a[i]-1]-a[i]个人 a[i+a[i]1]a[i]
直到第一个合法的 k k k 满足 max ⁡ k p = i { a p } = a [ k ] ≤ k − i + 1 \underset{p=i}{\overset{k}{\max}}\{a_p\}=a[k]\le k-i+1 p=imaxk{ap}=a[k]ki+1
若 找不到合法的 k k k ,则这些人与最大人数的队伍合并。

注 意 : 这 时 的 “ 最 大 人 数 的 队 伍 ” 我 们 已 经 选 择 完 毕 , 该 合 并 一 定 合 法 \color{red}注意:这时的“最大人数的队伍”我们已经选择完毕,该合并一定合法

【核心代码】

时间复杂度: O ( N log ⁡ N + N ) O(N\log N + N) O(NlogN+N)
就是排序+for循环的时间复杂度。

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 1e5+50;
int aa[MAX];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",&aa[i]);
    sort(aa+1,aa+1+n);
    int ren = 1;			/// ren表示目前该队伍中有多少人,默认值为 1
    int ans = 1;
    if(aa[n] > n)printf("-1");
    else{
        for(int i=1;i<=n - aa[n];++i){
            while(i<=n - aa[n] && ren < aa[i]){
                int cha = aa[i] - ren;
                ren += cha;
                i += cha;
            }
            if(i > n - aa[n])break;	/// 找不到合法的‘k’,与最大队伍合并,答案不变
            ans++;			/// 找到了合法的‘k’,新的队伍产生
            ren = 1;
        }
        printf("%d",ans);
    }
    return 0;
}

(二)其次考虑DP做法

定 义 d p [ i ] 表 示 到 第 i 个 人 位 置 , 最 大 合 法 队 伍 的 数 量 为 d p [ i ] \color{green}定义 dp[i]表示到第 i个人位置,最大合法队伍的数量为 dp[i] dp[i]idp[i]
首先,我们一样升序排序。
对于第 i i i 个人我们可以把它合并在第 i − 1 i-1 i1 个人的团队里面(如果存在的话)。
或者,我们选择 [ i − a [ i ] + 1 , i ] [i-a[i]+1,i] [ia[i]+1,i] 下标的人拉成新的一个队伍
当然这时我们 d p dp dp选择的是 d p [ i − a [ i ] + 1 − 1 ] dp[i-a[i]+1-1] dp[ia[i]+11],稍微想一想就能得到了。

那么状态转移方程就得到了:
d p [ i ] = max ⁡ { d p [ i − 1 ] d p [ i − a [ i ] ] + 1 如 果 i − a [ i ] + 1 存 在 dp[i] = \max \begin{cases} dp[i-1]\\ dp[i-a[i]]+1\qquad 如果i-a[i]+1存在 \end{cases} dp[i]=max{dp[i1]dp[ia[i]]+1ia[i]+1

补 充 修 改 : 最 后 的 答 案 应 该 选 择 d p [ n ] = d p [ n − a [ n ] ] + 1 这 是 为 了 保 证 合 法 性 的 考 虑 \color{red}补充修改:最后的答案应该选择dp[n]=dp[n−a[n]]+1\\这是为了保证合法性的考虑 dp[n]=dp[na[n]]+1
比如样例 { 3   1   1   3 } \{3 \,1 \,1 \,3\} {3113}
【感谢热心网友的Hack!】

【核心代码】

时间复杂度: O ( N log ⁡ N + N ) O(N\log N + N) O(NlogN+N)
就是排序+for循环的时间复杂度。
人的本质是复读机

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 2e5+50;
int aa[MAX];
int dp[MAX];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",&aa[i]);
    sort(aa+1,aa+1+n);
    int t;
    for(int i=1;i<=n;++i){
        int di = (i - aa[i]);
        t = 0;                        /// 选 [di+1,i]的人的时候的dp[i]的值
        if(di >= 0)t = dp[di] + 1;
        dp[i] = max(t,dp[i-1]);       /// 向左合并
    }
    printf("%d",t==0 ? -1 : t);       /// 为了保证合法,最后一支队伍要选择区间[n-a[n]+1,n]的范围
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值