种树 黑暗爆炸 - 2151(反悔型贪心)

Description
A城市有一个巨大的圆形广场,为了绿化环境和净化空气,市政府决定沿圆形广场外圈种一圈树。园林部门得到指令后,初步规划出n个种树的位置,顺时针编号1到n。并且每个位置都有一个美观度Ai,如果在这里种树就可以得到这Ai的美观度。但由于A城市土壤肥力欠佳,两棵树决不能种在相邻的位置(i号位置和i+1号位置叫相邻位置。值得注意的是1号和n号也算相邻位置!)。最终市政府给园林部门提供了m棵树苗并要求全部种上,请你帮忙设计种树方案使得美观度总和最大。如果无法将m棵树苗全部种上,给出无解信息。

思路
最近刚看了一下反悔贪心, 再做这题, 发现最难的还是要证明贪心的正确性,和构造正确的反悔贪心。
显然我们每次考虑当前的最大权值的点种与不种, 如  T Q A B C W E  7点中,B点权值最大, 但如果我们选了B点, 就无法选择 A C两点了, 就影响了 A B C 三点, Q,W可以随便选择
如果我们不选择 B 点 是否一定会选A, C 两点呢,
1, 显然 A, C不可能同时不选, 因为同时不选, 那么肯定会选B, 使结果更优秀。

2, A, C只选一个, 如果只选 C, 那么就不能选W, 此时的状况是 A B C W 的状态确定了, 只选择了C, 而没有选择 A B W,此时 T,E 不受影响,  显然此时和只选择B的情况是可以比较的 Q, W 就开始不受影响了,
显然如果W[C]< W[B], 这么选不会使结果更优(条件是 W[b]>W[c] && W[b] > W[A])
3,A C都选, 就有可能比只选择B更优秀。

做法, 用优先队列每次弹出可选的点中间的权值最大的点, 选出来以后再加入一个新点, 该点融合了ABC
三个点(设选了B) 权值为 W[A] + W[c] - W[b], 这样以后我们还可能用到这个点,如果以后再用到这个点, 就等于说后悔不选B了,而去选AC两点了
具体细节见于代码

#include <bits/stdc++.h>

using namespace std;
#define int long long
const int N = 1e6 + 10;

int last[N], Next[N], vis[N], a[N];
int sum, n, k, ans;
struct node
{
    int w, id;
    bool operator <(const node &t) const
    {
        return w < t.w;
    }
};

priority_queue<node> q;
void build()
{
    for(int i = 1; i <= n; i ++)
    {
        int x;
        scanf("%lld", a + i);
        x = a[i];
        last[i] = i - 1;
        Next[i] = i + 1;
        if(i == 1) last[i] = n;
        if(i == n) Next[i] = 1;
        q.push({x, i});
    }
}

void change(int x)
{
    vis[x] = true;
    Next[last[x]] = Next[x];
    last[Next[x]] = last[x];
    last[x] = Next[x] = 0;
}
void work()
{
    node x;
    for(int i = 1; i <= k; i ++)
    {
        while(vis[q.top().id]) q.pop();
        x = q.top();
       // cout << x.w << endl;
        q.pop();
        ans += x.w;
        int l = last[x.id], r = Next[x.id];
        a[x.id] = a[l] + a[r] - a[x.id];
        node now = (node){a[x.id], x.id};
        change(l), change(r);
        q.push(now);
    }
}
signed main()
{
    scanf("%lld%lld", &n, &k);
    build();
    if(k > n / 2)
    {
        cout << "Error!" << endl;
        return 0;
    }
    work();
    cout << ans << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值