【bzoj 2288】【POJ Challenge】生日礼物

本文介绍了一种使用贪心算法解决特定区间选择问题的方法。通过对输入序列进行预处理,去除0值并合并相邻符号相同的数值段,利用双向链表和堆结构实现了高效的区间合并过程,最终达到在限定条件下选取区间数量不超过给定值的目标。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://hzwer.com/2929.html


http://www.lydsy.com/JudgeOnline/problem.php?id=2288



只会DP的蒟蒻飘过

和hzwer的对拍了下没问题,为什么DP会wa捏?


----------------------------------------------------------------------------------------------------


如果数据范围小这题可以dp

f[i][j]表示前i个取j段这样。。

不会做于是膜拜了seter的题解

似乎是贪心的做法

首先将符号相同的一段合并成一个值,为了处理方便一开始可以将序列中的0全部去掉

如 1 3 -1 -2 4 -1 变成 4 -3 4 -1

容易证明最后结果一定是取完整的一段

如果>0的段小于m那么就解决了

如果大于m

将这些段放入堆中按绝对值进行排序

获取堆中最小值将它和周围两段合并,这里要用到双向链表

因为如果这个值是正数,那么相当于不选它了

是负数的话相当于将它左右两段合并,这样都使得选取的段数-1

但是要考虑一个边界的问题

边上的负数不能取,因为就算取了它也不存在左右两段合并

而正数可以取,相当于不选它了

这里纠结了一天。。。代码写太傻逼一直wa

学长20分钟1A了只能跪舔。

以后输入输出都写快速读入吧,似乎很优越




#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#define inf 0x7fffffff
using namespace std;
struct heap{int v,x,av;}h[200010];
int n,m,a[100010];
int tmp,sz,ans,tot;
int v[100010],cnt;
int next[100010],pre[100010],pos[100010];
inline int read()
{
    char ch=getchar();
	int f=1,x=0;
    while(!(ch>='0'&&ch<='9')){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}
void pushup(int x)
{
    while(h[x].av<h[x>>1].av)
    {
        pos[h[x>>1].x]=x;
        swap(h[x],h[x>>1]);
        x=x>>1;
    }
    pos[h[x].x]=x;
}
void push(int v,int x)
{
    sz++;h[sz].v=v;h[sz].x=x;h[sz].av=abs(v);
    pos[x]=sz;
    pushup(sz);
}
void pushdown(int x)
{
    int to;
    while((x<<1)<=sz)
    {
        to=(x<<1);
        if(to<sz && h[to].av>h[to+1].av)to++;
        if(h[x].av>h[to].av)
        {
            pos[h[to].x]=x;
            swap(h[to],h[x]);
            x=to;
        }
        else break;  
    }
    pos[h[x].x]=x;
}
void del(int x)
{
    h[x].av=inf;
	pushdown(x);
}
void init()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        a[++tmp]=read();
        if(a[tmp]==0)tmp--;
    }
    v[++cnt]=a[1];
    for(int i=2;i<=tmp;i++)
    {
        if((a[i]>0&&a[i-1]>0)||(a[i-1]<0&&a[i]<0))v[cnt]+=a[i];
        else v[++cnt]=a[i]; 
    }
    for(int i=1;i<=cnt;i++)if(v[i]>0){ans+=v[i];tot++;}
    for(int i=1;i<=cnt;i++){next[i]=i+1;pre[i]=i-1;}
    pre[1]=-1;next[cnt]=-1;
}
void solve()
{
    int a,b;heap k;
    for(int i=1;i<=cnt;i++)push(v[i],i);
    while(tot>m)
    {
         k=h[1];
         if(pre[k.x]==-1)
         {
             if(k.v>0){ans-=k.v;del(1);}
             else{del(1);tot++;}
             pre[next[k.x]]=-1;
         }
         else if(next[k.x]==-1)
         {
             if(k.v>0){ans-=k.v;del(1);}
             else{del(1);tot++;}
             next[pre[k.x]]=-1;
         }
         else
         {
             ans-=k.av;
             a=next[k.x];b=pre[k.x];
             pre[k.x]=pre[b];next[pre[b]]=k.x;
             next[k.x]=next[a];pre[next[a]]=k.x;
             h[1].av=h[pos[a]].av+h[pos[b]].av-h[1].av;
             h[1].v+=h[pos[a]].v+h[pos[b]].v;
             pushdown(1);
             del(pos[a]);del(pos[b]);
         }
         tot--;
    }
    printf("%d\n",ans);
}
int main()
{
    init();
    solve();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值