bzoj 1150&2151&2288(双向链表+堆)(贪心)

  经典模型:在n个点中选k个点,要求两两不相邻,且总权值最大/最小。

  做法:用双向链表串起来,把所有点丢进堆里,选择一个点的时候把它左右两个点从双向链表和堆中去除,然后把这个点的权值加进ans,出堆后改为左右两边的权值-当前权值重新入堆,重复k次,ans即为答案

  原理:左右两边的权值-当前权值相当于这个点不选,改为选左右两边的点,并且多选了一个点,重复k次后必然取到k个点

  三道同类型题

  bzoj1150:显然一段网线肯定接在相邻的两个点,于是把相邻的点的距离求出来,问题就变成经典模型了

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cmath>
#include<map>
#define ll long long
using namespace std;
const int maxn=500010,inf=1e9;
struct poi{int pos,dis;};
priority_queue<poi>q;
bool operator<(poi a,poi b){return a.dis>b.dis;}
int n,k,a[maxn],dis[maxn],pre[maxn],next[maxn];
ll ans;
bool v[maxn];
void read(int &k)
{
    int f=1;k=0;char c=getchar();
    while(c<‘0‘||c>‘9‘)c==‘-‘&&(f=-1),c=getchar();
    while(c<=‘9‘&&c>=‘0‘)k=k*10+c-‘0‘,c=getchar();
    k*=f;
}
inline void del(int x)
{
    int l=pre[x],r=next[x];
    pre[x]=next[x]=0;
    next[l]=r;pre[r]=l;
}
int main()
{
    read(n);read(k);
    for(int i=1;i<=n;i++)read(a[i]);
    for(int i=1;i<n;i++)dis[i]=a[i+1]-a[i],q.push((poi){i,dis[i]});dis[0]=dis[n]=inf;
    for(int i=1;i<n;i++)pre[i]=i-1,next[i]=i+1;
    for(int i=1;i<=k;i++)
    {
        poi t=q.top();
        while(v[t.pos])q.pop(),t=q.top();
        ans+=t.dis;v[pre[t.pos]]=v[next[t.pos]]=1;
        dis[t.pos]=dis[pre[t.pos]]+dis[next[t.pos]]-dis[t.pos];
        q.pop();q.push((poi){t.pos,dis[t.pos]});
        del(pre[t.pos]);del(next[t.pos]);
    }
    printf("%lld\n",ans);
    return 0;
}

  bzoj2151:双向链表首尾串起来,问题就变成经典模型了

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cmath>
#include<map>
#define ll long long
using namespace std;
const int maxn=500010,inf=1e9;
struct poi{int pos,dis;};
priority_queue<poi>q;
bool operator<(poi a,poi b){return a.dis<b.dis;}
int n,k,a[maxn],dis[maxn],pre[maxn],next[maxn];
ll ans;
bool v[maxn];
void read(int &k)
{
    int f=1;k=0;char c=getchar();
    while(c<‘0‘||c>‘9‘)c==‘-‘&&(f=-1),c=getchar();
    while(c<=‘9‘&&c>=‘0‘)k=k*10+c-‘0‘,c=getchar();
    k*=f;
}
inline void del(int x)
{
    int l=pre[x],r=next[x];
    pre[x]=next[x]=0;
    next[l]=r;pre[r]=l;
}
int main()
{
    read(n);read(k);
    if(k>n>>1)return puts("Error!"),0;
    for(int i=1;i<=n;i++)read(dis[i]),q.push((poi){i,dis[i]});
    for(int i=1;i<=n;i++)pre[i]=i-1,next[i]=i+1;pre[1]=n;next[n]=1;
    for(int i=1;i<=k;i++)
    {
        poi t=q.top();
        while(v[t.pos])q.pop(),t=q.top();
        ans+=t.dis;v[pre[t.pos]]=v[next[t.pos]]=1;
        dis[t.pos]=dis[pre[t.pos]]+dis[next[t.pos]]-dis[t.pos];
        q.pop();q.push((poi){t.pos,dis[t.pos]});
        del(pre[t.pos]);del(next[t.pos]);
    }
    printf("%lld\n",ans);
    return 0;
}

  bzoj2288:

  这题比较复杂。首先可以发现正数连续的一段和负数连续的一段要取肯定是都得同时取的,那么就可以把连续的同正负的数缩成一个了。然后如果正数个数不大于k,直接取所有正数即可。如果正数个数大于k,我们考虑舍弃一些正数,或者选择一些负数。只要将所有数取绝对值,问题就变成经典模型了。为什么呢?

  我们选择的是总权值最小的数,如果他是个正数,相当于舍弃,如果是负数,相当于把左右两端的正数连成一段。显然不可能同时选两个相邻的数,否则一定是正负一起选,那负数完全可以不选,因为既然你要丢弃这个正数,为何还要选择一个负数呢,选择负数的作用只有连接左右两端的正数。

  因此,这个题也能转化为经典模型,就可以写了。

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cmath>
#include<map>
#define ll long long
using namespace std;
const int maxn=500010,inf=1e9;
struct poi{int pos,dis;};
priority_queue<poi>q;
bool operator<(poi a,poi b){return a.dis>b.dis;}
int n,m,flag,N,now,l,r,ans;
int a[maxn],s[maxn],pre[maxn],next[maxn];
bool v[maxn];
void read(int &k)
{
    int f=1;k=0;char c=getchar();
    while(c<‘0‘||c>‘9‘)c==‘-‘&&(f=-1),c=getchar();
    while(c<=‘9‘&&c>=‘0‘)k=k*10+c-‘0‘,c=getchar();
    k*=f;
}
inline void del(int x)
{
    int l=pre[x],r=next[x];
    pre[x]=next[x]=0;
    pre[r]=l;next[l]=r;
}
int main()
{
    read(n);read(m);
    for(int i=1;i<=n;i++)read(a[i]);
    int l=1,r=n;
    while(a[l]<=0&&l<=n)l++;
    while(a[r]<=0&&r)r--;
    for(int i=l;i<=r;)
    {
        if(a[i]>=0)flag=1;else flag=-1;
        for(now=0;flag*a[i]>=0&&i<=r;i++)now+=a[i];
        s[++N]=now;
    }
    for(int i=1;i<=N;i+=2)ans+=s[i];
    if(m>=(N+1)>>1)return printf("%d\n",ans),0;
    for(int i=1;i<=N;i++)q.push((poi){i,s[i]=(s[i]>0?s[i]:-s[i])});
    for(int i=1;i<=N;i++)pre[i]=i-1,next[i]=i+1;s[0]=inf;s[N+1]=inf;
    int up=((N+1)>>1)-m;
    for(int i=1;i<=up;i++)
    {
        poi t=q.top();
        while(v[t.pos])q.pop(),t=q.top();q.pop();
        s[t.pos]=s[pre[t.pos]]+s[next[t.pos]]-s[t.pos];
        ans-=t.dis;q.push((poi){t.pos,s[t.pos]});
        v[pre[t.pos]]=v[next[t.pos]]=1;
        del(pre[t.pos]);del(next[t.pos]);
    }
    printf("%d\n",ans);
    return 0;
}

时间: 2024-11-05 16:34:44

bzoj 1150&2151&2288(双向链表+堆)(贪心)的相关文章

BZOJ 2802 Poi2012 Warehouse Store 堆+贪心

题目大意:有n天,早上进货中午卖,可以选择卖或者不卖,问最多可以卖出多少人 首先贪心的思想是如果当前能卖就卖 但是这样不一定是最优的 比如说我第一天来一个人把所有的库存都买走了 然后后面基本没有补给 后面的人都饿死了 因此我们维护一个大根堆来记录我们都卖出了多少份 如果有一个人买不到 我们去大根堆里寻找有没有买的比他多的 如果有 把之前的人取消 卖给这个人 这样虽然不能增加答案 但是可以增加库存 #include <cstdio> #include <cstring> #inclu

BZOJ 1029: [JSOI2007]建筑抢修 堆+贪心

1029: [JSOI2007]建筑抢修 Description 小刚在玩JSOI提供的一个称之为“建筑抢修”的电脑游戏:经过了一场激烈的战斗,T部落消灭了所有z部落的入侵者.但是T部落的基地里已经有N个建筑设施受到了严重的损伤,如果不尽快修复的话,这些建筑设施将会完全毁坏.现在的情况是:T部落基地里只有一个修理工人,虽然他能瞬间到达任何一个建筑,但是修复每个建筑都需要一定的时间.同时,修理工人修理完一个建筑才能修理下一个建筑,不能同时修理多个建筑.如果某个建筑在一段时间之内没有完全修理完毕,这

BZOJ 1528 POI2005 sam-Toy Cars 堆+贪心

题目大意:有n个玩具,都放在架子上,地板上能放k个,要玩p次玩具,如果不在地板上就要去架子上拿,地板满了要放回去,求最少操作次数 贪心思想:每次放回玩具时选择下次玩的时间最靠后的玩具放回去 可以用堆来模拟这一贪心过程 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M 500500 using namespace std; typede

BZOJ 1150--数据备份(链表&amp;堆&amp;贪心)

1150: [CTSC2007]数据备份Backup Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2572  Solved: 1038[Submit][Status][Discuss] Description 你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份.然而数据备份的工作是枯燥乏味的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣.已知办公楼都位于同一条街上.你决定给这些

POJ3253 Fence Repair 小顶堆+贪心

给了你N个木棒,求把他们组装成一根需要的最小花费,每次只能选两根组装在一起,需要的花费为两个木棒之和, 以前遇到过把一整根切开的,那个是DP,这个则有些类似,可是大胆的猜测了一下,直接每次选取所有木棒中最短的两根,这样就可以了,那么贪心是适用的,但是数量很多,而且两根最短的组装好了得插回去,这样不可能每次都排序吧, 这题首先优先队列肯定是可以做的, 最小堆也是可以的,每次都选出堆里的最小的两个求和再放回去即可 队列本身也就是堆吧,所以差别不大,但是没用过最小堆最大堆的 所以用一次把 #inclu

P4053 [JSOI2007]建筑抢修 堆贪心

思路:堆贪心 提交:1次 题解: 先按时间\(sort\),然后如果能修就直接扔堆里,不能修取堆顶比一下时间长短,把时间短的扔进堆: #include<cstdio> #include<iostream> #include<queue> #include<algorithm> #define ull unsigned long long #define ll long long #define R register int using namespace s

BZOJ 1150 CTSC2007 数据备份Backup 堆+贪心

题目大意:给定一个长度为n?1的序列,要求选出k个不相邻的数使得和最小 费用流显然能跑,而且显然过不去- - 考虑用堆模拟费用流 一个错误的贪心是每次取最小,这样显然过不去样例 我们把[每次取最小]改为[每次选择一个区间取反],用堆来维护这些区间即可 每次取出最小的区间,然后将两边合并 (比如现在堆里有[1,3][4,4][5,5])这三个区间,我取走了[4,4]并计入答案,那么我删除[1,3]和[5,5]这两个区间,并加入[1,5]这个区间,权值为[1,3]的权值+[5,5]的权值-[4,4]

【BZOJ 1150】 1150: [CTSC2007]数据备份Backup (贪心+优先队列)

1150: [CTSC2007]数据备份Backup Description 你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份.然而数据备份的工作是枯燥乏味 的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣.已知办公 楼都位于同一条街上.你决定给这些办公楼配对(两个一组).每一对办公楼可以通过在这两个建筑物之间铺设网 络电缆使得它们可以互相备份.然而,网络电缆的费用很高.当地电信公司仅能为你提供 K 条网络电缆,这意味 着你仅

bzoj 1577: [Usaco2009 Feb]庙会捷运Fair Shuttle——小根堆+大根堆+贪心

Description 公交车一共经过N(1<=N<=20000)个站点,从站点1一直驶到站点N.K(1<=K<=50000)群奶牛希望搭乘这辆公交车.第i群牛一共有Mi(1<=Mi<=N)只. 他们希望从Si到Ei去.公交车只能座C(1<=C<=100)只奶牛.而且不走重复路线,请计算这辆车最多能满足多少奶牛听要求.注意:对于每一群奶牛,可以部分满足,也可以全部满足,也可以全部不满足. Input 第1行: 三个整数: K,N,C. 由空格隔开. 第2..