Luogu P1986 元旦晚会(贪心)

题目传送门

这不就是道纯纯的贪心水题吗?尽可能的让话筒在同时身处多声部的同学手中。


我的第一个思路:就先在输入时用差分处理每个位置,记录在这个位置上的同学同时在的声部数。

	cin >> n >> m;
    for(int i = 1; i <= m; i++){
        cin >> sing[i].left >> sing[i].right >> sing[i].num;
        //一维差分
        chafen[sing[i].left] += 1;
        chafen[sing[i].right + 1] -= 1;
    }
	for(int i = 1; i <= n; i++){
        //当前位置要继承上一个位置额外加的值,那么段首的+1就可以使全段落所有位置全部+1
        sum[i] = sum[i-1] + chafen[i];
    }

然后依次对每个声部进行操作,先判断这个声部放了几个话筒,再补足缺的话筒数。在补话筒时,不仅要保证当前同学手中没有话筒,还要保证当前同学同时身处的声部数是同声部所有没拿话筒的同学中最多的,以求最终放置的话筒数量最少。

for(int i = 1; i <= m; i++){
        //判断该声部放了几个话筒,是否还需要继续放
        int cnt = 0;//该声部当前放的话筒数量
        for(int j = sing[i].left; j <= sing[i].right; j++){
            if(mark[j])cnt++;
        }
        while(cnt < sing[i].num){
            int maxx = 0, pos;
            for(int j = sing[i].left; j <= sing[i].right; j++){
                if(sum [j] > maxx && !mark[j]){
                    maxx = sum[j];
                    pos = j;
                }
            }
            cnt++;
            ans++;
            mark[pos] = 1;
        }
    }

可惜…WA了9个点…呜呜呜我再也不说它是水题了…


仔细想了一下,上面那个思路存在很大的漏洞,即每次循环寻找目标同学时,判断条件都是该同学身处的声部数是否为同声部最多,但却忽略了最优解要保证每个声部需要的话筒数量刚好为c[i],即最小数量

在这里插入图片描述

如图示情况,如果4->5->6声部与7->8->9声部都只需要 1 1 1个话筒,而10->11->12声部需要2个话筒,0->1->2->3->4声部需要3个话筒,那么最好的放话筒方法应该是放在 0 ( 5 ) ( 8 ) 0(5)(8) 0(5)(8) 2 ( 10 ) 2(10) 2(10) 3 ( 11 ) 3(11) 3(11)三位同学手中。但是按照上述原则,代码在执行过程中会选择将话筒放在 0 ( 5 ) ( 8 ) 、 1 ( 6 ) ( 9 ) 0(5)(8)、1(6)(9) 0(5)(8)1(6)(9) 2 ( 10 ) 2(10) 2(10)手中,就不是全局,甚至不是真正意义上的局部最优解了!因此这个思路不可取。


正确的思路也很简单,就是将所有声部按照右边界从小到大的顺序排序,如果右边界相同就将左边界小的排在前面。对于每一个声部,从右向左放话筒,放够为止。这样就可以保证重叠部分的人在话筒数量还不够时优先拿到话筒,同时保证每个话筒价值最大化,保证每个声部的话筒都是当前声部所需要的最小数量。

在这里插入图片描述

以图示情况为例,若0->1->2声部与3->4声部都只需要一个,5->6->7声部需要两个话筒,那么根据从右向左依次对每个声部放话筒的原则,会把话筒放在 2 ( 4 ) ( 5 ) 2(4)(5) 2(4)(5) 7 7 7,恰是局部最优解,也是全局最优解。

细节请看AC代码

#include <iostream>
#include <algorithm>
#include <cstdio>

using namespace std;
int n, m;//n个同学,m个声部
bool mark[30005];
int ans;

struct voice{
    int left = 0;//声部左边界
    int right = 0;//声部右边界
    int num = 0;//声部所需最小话筒数
}sing[5005];

bool cmp(voice a, voice b){
    if(a.right != b.right)return a.right < b.right;
    else return a.left < b.left;
}

int main(){

    //freopen("code.in", "r", stdin);
    //freopen("code.out", "w", stdout);

    cin >> n >> m;
    for(int i = 1; i <= m; i++){
        cin >> sing[i].left >> sing[i].right >> sing[i].num;
    }
    sort(sing + 1, sing + 1 + m, cmp);

    for(int i = 1; i <= m; i++){
        int cnt = 0;//当前声部已有话筒数
        for(int j = sing[i].left; j <= sing[i].right; j++){
            if(mark[j])cnt++;//先记录当前声部已有的话筒数量
        }
        if(cnt < sing[i].num){//如果不够
            int j = sing[i].right;
            while(cnt < sing[i].num && j >= sing[i].left){//从右向左放话筒
                if(!mark[j]){//如果当前同学手中没有话筒
                    mark[j] = 1;//标记为有
                    cnt++;//声部话筒数+1
                    ans++;//总话筒数+1
                    
                }
                j--;//向左寻找
            }
        }
    }

    cout << ans;
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值