bzoj3502[PA2012]Tanie Linie(最大k区间和)

题意:给定一个长为n的数列,要求选出最多k个不相交的区间(可以不选),使得选中的数字之和最大.(1<=k<=n<=1000000)
分析:首先我们通过预处理对问题做一些简化.原序列中的0对答案没有影响,可以直接删掉.连续的一段正数或一段负数一定是都选或者都不选,可以合并成一个数字.这样把序列转化成了正数和负数交替出现的形式.如果序列的最左端/最右端是负数,这个负数在最优解当中一定不会被选中,我们可以把它删掉.这样就把序列变成了正负交替,以正数开头和结尾的形式.
这时若直接考虑选出k个不相交区间,仍然不好做.这里一个关键是先不考虑k的限制得到一个最优解,然后再对这个解进行调整使得它满足k的限制.
不考虑k的限制,最优解显然就是只选择所有正数,如果这时已经满足k的限制,就直接输出答案,否则我们需要进一步观察性质.
一开始,怎样修改这个解才能使得区间数目减少1呢?有两种方法:
1. 将某个正数由选中变为不选中,区间数减1
2. 将某个负数由不选中变为选中,可以连接两侧的区间使得区间数减1
显然,不论怎样操作,答案都会变小,这个变小的值就是我们修改状态的数的绝对值,那么第一步可以贪心的,现在的问题是怎样实现连续的贪心,并且每一步操作都让区间数减1.
考虑连续的三个数字… (左边一堆数)...a,b,c…(右边一堆数)…,不妨设a<0,c<0,b>0,如果b的权值最小,我们在第一步将b从选变成不选,那么之后单独将a或c从不选变成选就不能减少区间个数,我们打个标记将这两种决策删除掉.但这时有另外一种反悔的决策:同时选中abc三个数,这样可以让区间数再次减1.如果中间的数字是负数,也同理会使接下来可行的决策数目减少2,然后增加一种新的可行决策.我们用一个堆维护所有的决策(priority_queue足矣0),用一个链表维护每个位置前后第一个没有被删除的决策的位置,复杂度O(nlogn)
注意一个边界情况:a,b….(a的左边没有更多数字了,a>0,b<0),如果我们在选b之前将a从选中变为不选中,那么下一步同时选中ab将无法使区间数目减少(a的左边没有一个区间可以和右边连接),此时应当不添加新的决策.(这里我们在选中b之前选中了a,说明ab之和小于0,要么只选a,要么都不选,如果ab都选了一定没有都不选优).如果选a之前选中了b,这时将ab所在的区间变成都不选可以减少区间个数,不需要特殊处理.
类似于bzoj1150数据备份和bzoj2151种树.细节各种狗,一定要对拍…对拍可以费用流,也可以O(n^2)DP

#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
const int maxn=1000005;
ll a[maxn];
bool del[maxn];
struct node{
  ll w;int pos;
  node(ll a,int b){
    w=a;pos=b;
  }
  bool operator <(const node &B)const{
    return w<B.w;
  }
};
priority_queue<node> q;
int pre[maxn],nxt[maxn];ll val[maxn];
int main(){
  int n,k;scanf("%d%d",&n,&k);
  ll x;
  int tot=1;
  scanf("%lld",&a[1]);
  for(int i=2;i<=n;++i){
    scanf("%lld",&x);//printf("%d\n",x);
    if(x==0)continue;
    if((x>=0)==(a[tot]>=0)||a[tot]==0)a[tot]+=x;
    else a[++tot]=x;
  }
  ll sum=0;int cnt=0;
  for(int i=1;i<=tot;++i){
    if(a[i]>=0){//printf("%lld\n",a[i]);
      sum+=a[i];cnt++;
    }
  }
  if(cnt<=k){//printf("!");
    printf("%lld\n",sum);
  }else{
    k=cnt-k;
    int l=1,r=tot;
    if(a[1]<0)l++;
    if(a[tot]<0)r--;
    if(tot==1&&a[1]<0){
      printf("0\n");
    }else{
      for(int i=l;i<r;++i)nxt[i]=i+1;
      for(int i=l+1;i<=r;++i)pre[i]=i-1;
      for(int i=l;i<=r;++i)val[i]=(a[i]>=0)?(-a[i]):a[i];
      for(int i=l;i<=r;++i)q.push(node(val[i],i));
      while(k--){//printf("!");
        while(del[q.top().pos])q.pop();
        node tmp=q.top();q.pop();
        int i=tmp.pos;//printf("%d\n",tmp.w);
        sum+=tmp.w;del[pre[i]]=del[nxt[i]]=true;
        if(pre[i]&&nxt[i]){
          val[i]=val[pre[i]]+val[nxt[i]]-val[i];
          q.push(node(val[i],i));
          pre[i]=pre[pre[i]];nxt[i]=nxt[nxt[i]];
          if(nxt[i])pre[nxt[i]]=i;
          if(pre[i])nxt[pre[i]]=i;
        }else{
                  del[i]=true;pre[i]=pre[pre[i]];nxt[i]=nxt[nxt[i]];
          if(nxt[i])pre[nxt[i]]=pre[i];
          if(pre[i])nxt[pre[i]]=nxt[i];
        }
      }
    }
    printf("%lld\n",sum);
  }
  return 0;
}
时间: 2024-11-08 22:08:40

bzoj3502[PA2012]Tanie Linie(最大k区间和)的相关文章

【BZOJ3502/2288】PA2012 Tanie linie/【POJ Challenge】生日礼物 堆+链表(模拟费用流)

[BZOJ3502]PA2012 Tanie linie Description n个数字,求不相交的总和最大的最多k个连续子序列. 1<= k<= N<= 1000000. Sample Input 5 2 7 -3 4 -9 5 Sample Output 13 题解:跟1150和2151差不多. 我们先做一些预处理,因为连续的正数和连续的负数一定是要么都选要么都不选,所以可以将它们合并成一个数,同时区间中的零以及左右两端的负数没有意义,可以将它们删掉.然后我们得到的序列就变成:正-

【BZOJ】3502 PA2012 Tanie linie

[算法] [题解] 胡策k≤10的环状DP做法: 1.钦定法:先确定第一位(可能和第n位)的状态,然后后面正常做DP,显然正确答案是一定会被记录的,因为从整体上看不会有影响. 2.环的特性:取的段和不取的段数量相等,位置互补.所以1和n的连接处都选或都不选都会有不被包括的情况,一选一不选就和链一样了. 正解贪心: 因为相邻的正数或相邻的负数肯定是要选一起选,所以点缩成正负正负-的数列形式,那么考虑先选择全部正的. 如果选择的段数过多,考虑删除则有两种选择:舍弃一个正数(相当于舍弃一个正数和两个负

瞎题表

数据结构小练习bzoj3221:[Codechef FEB13] Obserbing the tree树上询问 树剖+主席树(区间修改,加等差数列)bzoj2735:世博会 主席树+切比雪夫距离转曼哈顿距离+我最弱的数学推理bzoj3217:ALOEXT 替罪羊套01Trie(我的码力还是弱得不行,傻逼错误一大堆……)精细的实现果然很重要(对于数据结构里的点(比如……),可以直接提取其维护的区间然后操作的啊)替罪羊的插入重建好像使得重建点的祖先的cover不正确了?然而并没有什么影响?(zyf提

【BZOJ-3638&amp;3272&amp;3267&amp;3502】k-Maximum Subsequence Sum 费用流构图 + 线段树手动增广

3638: Cf172 k-Maximum Subsequence Sum Time Limit: 50 Sec  Memory Limit: 256 MBSubmit: 174  Solved: 92[Submit][Status][Discuss] Description 给一列数,要求支持操作: 1.修改某个数的值 2.读入l,r,k,询问在[l,r]内选不相交的不超过k个子段,最大的和是多少. Input The first line contains integer n (1 ≤ n 

Super Mario(线段树离线区间k值)

以前见过这题,没做出来,知道是离线处理,这次仔细想了下, 首先把出现的高度都map离散化一下,以离散化出来的数目g建树,把每个位置都开俩个vector,一个存以这个位置为L的询问,一个存以这个位置为R的询问. 然后从1-g 进行更新,假如当前i是以第j个区间的开始位置,那么这时就可以询问一下<=p[j].h的个数s,显然这时第J个区间多加的,需要减掉,p[j].sum-=s; 然后更新第i个数,update(a[i],1,g,1);再找到某第k区间是以i结尾的,那么依旧询问一下,得出s,p[k]

主席树的各类模板(区间第k大数【动,静】,区间不同数的个数,区间&lt;=k的个数)

取板粗 1.(HDOJ2665)http://acm.hdu.edu.cn/showproblem.php?pid=2665 (POJ2104)http://poj.org/problem?id=2104 (POJ2761)http://poj.org/problem?id=2761 题意:求区间第K大,主席树模板题 #include<cstdio> #include<cstring> #include<algorithm> using namespace std; t

hdu 4570 Multi-bit Trie 区间DP入门

Multi-bit Trie 题意:将长度为n(n <= 64)的序列分成若干段,每段的数字个数不超过20,且每段的内存定义为段首的值乘以2^(段的长度):问这段序列总的内存最小为多少? 思路:区间的最值,区间DP; 枚举长度,在初始化时,将长度和20比较,小于20看成是一段,大于20时,因为不能压缩,直接全部分割就是了:之后枚举区间内部的所有值,这是并不需要考虑将这个区间一分为二后各自的长度是否大于20,因为在子结构中已经计算好了:直接去最优解即可: #include<iostream>

树状数组求区间最值

树状数组求区间最值 树状数组(Binary Index Tree)利用二进制的一些性质巧妙的划分区间,是一种编程,时间和空间上都十分理想的求区间和的算法,同样我们可以利用树状数组优美的区间划分方法来求一个序列的最值 约定以 num[]  表示原数组, 以 idx[] 表示索引数组, Lowbit(x)=x&(-x) 树状数组求和时通过构造数组 idx[] 使 idx[k]=sum(num[tk]), tk [k-Lowbit(k)+1,k], 使用同样的方法构造最值索引数组: 以最大值为例, 先

hdu 6049---Sdjpx Is Happy(区间DP+枚举)

题目链接 Problem Description Sdjpx is a powful man,he controls a big country.There are n soldiers numbered 1~n(1<=n<=3000).But there is a big problem for him.He wants soldiers sorted in increasing order.He find a way to sort,but there three rules to obe