BZOJ4504 : K个串

从左往右枚举右端点,用一棵线段树维护每个左端点的去重后的区间和。

那么对于$a[r]$,需要在$[pre[a[r]]+1,r]$里区间加上$a[r]$。

将线段树可持久化,并维护区间最大值,就可以在线询问形如“给定$r$以及$a,b$”,问$l$在$[a,b]$里$[l,r]$的区间和的最大值的问题。

用一个大根堆维护五元组$(v,x,l,r,m)$,表示区间和为$v$,所在线段树根节点为$x$,所选左端点范围为$[l,r]$,选了$m$。

然后重复$k$次,每次取出堆顶,扩展出$[l,m-1]$以及$[m+1,r]$两个新状态。

时间复杂度$O((n+k)\log n)$。

#include<cstdio>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
typedef long long ll;
typedef pair<ll,int>P;
const int N=100010,M=7000000;
int n,m,i,x,tot,T[N],l[M],r[M];ll tag[M];P v[M];map<int,int>pre;
struct E{
  ll v;int x,l,r,m;
  E(){}
  E(ll _v,int _x,int _l,int _r,int _m){v=_v,x=_x,l=_l,r=_r,m=_m;}
  inline bool operator<(const E&b)const{return v<b.v;}
}tmp;
priority_queue<E>Q;
inline void read(int&a){
  char c;bool f=0;a=0;
  while(!((((c=getchar())>=‘0‘)&&(c<=‘9‘))||(c==‘-‘)));
  if(c!=‘-‘)a=c-‘0‘;else f=1;
  while(((c=getchar())>=‘0‘)&&(c<=‘9‘))(a*=10)+=c-‘0‘;
  if(f)a=-a;
}
int build(int a,int b){
  int x=++tot;
  v[x]=P(0,a);
  if(a==b)return x;
  int mid=(a+b)>>1;
  l[x]=build(a,mid),r[x]=build(mid+1,b);
  return x;
}
inline int add(int x,ll p){
  int y=++tot;
  l[y]=l[x],r[y]=r[x],v[y]=v[x],tag[y]=tag[x]+p;
  v[y].first+=p;
  return y;
}
inline void pb(int x){
  if(!tag[x])return;
  l[x]=add(l[x],tag[x]);
  r[x]=add(r[x],tag[x]);
  tag[x]=0;
}
int change(int x,int a,int b,int c,int d,int p){
  if(c<=a&&b<=d)return add(x,p);
  pb(x);
  int y=++tot;
  l[y]=l[x],r[y]=r[x],v[y]=v[x];
  int mid=(a+b)>>1;
  if(c<=mid)l[y]=change(l[x],a,mid,c,d,p);
  if(d>mid)r[y]=change(r[x],mid+1,b,c,d,p);
  v[y]=max(v[l[y]],v[r[y]]);
  return y;
}
P ask(int x,int a,int b,int c,int d){
  if(c<=a&&b<=d)return v[x];
  pb(x);
  int mid=(a+b)>>1;
  if(d<=mid)return ask(l[x],a,mid,c,d);
  if(c>mid)return ask(r[x],mid+1,b,c,d);
  return max(ask(l[x],a,mid,c,d),ask(r[x],mid+1,b,c,d));
}
inline void ext(int x,int l,int r){
  if(l>r)return;
  P t=ask(x,1,n,l,r);
  Q.push(E(t.first,x,l,r,t.second));
}
int main(){
  read(n),read(m);
  for(T[0]=build(1,n),i=1;i<=n;i++){
    read(x);
    T[i]=change(T[i-1],1,n,pre[x]+1,i,x);
    pre[x]=i;
    ext(T[i],1,i);
  }
  while(m--){
    tmp=Q.top();Q.pop();
    ext(tmp.x,tmp.l,tmp.m-1);
    ext(tmp.x,tmp.m+1,tmp.r);
  }
  return printf("%lld",tmp.v),0;
}

  

时间: 2024-10-07 19:54:59

BZOJ4504 : K个串的相关文章

K Seq HihoCoder - 1046 || BZOJ4504 k个串

这题与超级钢琴类似,然而重复的不重复计算贡献.. 那么先求出数组nxt,nxt[i]表示第i个元素之后的第一个与其相等的元素的下标,不存在则nxt[i]=0 考虑取的区间左端点为1时的情况. 将读入序列a中相等元素都只保留最先出现的,其余变为0,然后求前缀和,得到数组b. 此时可以知道,设f(l,r)为取下标在[l,r]区间内数时的答案,那么f(1,r)=b[r]. 考虑取的区间左端点为2时的情况.如何维护b数组,使得新的b数组也满足f(2,r)=b[r]? 手模样例区间左端点为1和2时符合要求

数据结构(主席树):COGS 2213. K个串

2213. K个串 ★★★★   输入文件:bzoj_4504.in   输出文件:bzoj_4504.out   简单对比时间限制:20 s   内存限制:512 MB [题目描述] 兔子们在玩k个串的游戏.首先,它们拿出了一个长度为n的数字序列,选出其中的一 个连续子串,然后统计其子串中所有数字之和(注意这里重复出现的数字只被统计一次). 兔子们想知道,在这个数字序列所有连续的子串中,按照以上方式统计其所有数字之和,第 k大的和是多少. [输入格式] 第一行,两个整数n和k,分别表示长度为n

[bzoj P4504] K个串

[bzoj P4504] K个串 [题目描述] 兔子们在玩k个串的游戏.首先,它们拿出了一个长度为n的数字序列,选出其中的一个连续子串,然后统计其子串中所有数字之和(注意这里重复出现的数字只被统计一次). 兔子们想知道,在这个数字序列所有连续的子串中,按照以上方式统计其所有数字之和,第k大的和是多少. [输入格式] 第一行,两个整数n和k,分别表示长度为n的数字序列和想要统计的第k大的和 接下里一行n个数a_i,表示这个数字序列 [输出格式] 一行一个整数,表示第k大的和 [样例输入] 7 5

spoj 7258 SUBLEX(求第k大字串

其实对sam的拓扑排序我似懂非懂但是会用一点了. /** @xigua */ #include <stdio.h> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <cstring> #include <queue> #include <set>

csu1563: Lexicography以及找出多重集的第k个串的讲解

Time Limit: 1 Sec  Memory Limit: 128 MB Submit: 162  Solved: 52 [Submit][Status][Web Board] Description An anagram of a string is any string that can be formed using the same letters as the original. (We consider the original string an anagram of its

4504: K个串 主席树+优先队列

这道题因为有一个数在序列中出现多次只算一次的限制.我们可以这样搞.假设在当前题意下求给定右端点的区间最值.那么我们可以预处理出每个数前一次出现的位置pre[i] .接下来从左到右加入每一个值,就是在 pre[i] + 1 —— i 这个区间内加上 v[i] 的值,这样就可以得到以当前 i 点为右端点的各个区间的值(很明显维护一下最值就好了).接下来很明显有n个版本的线段树(如果你说一开始那个空的线段树也算一个版本的话,就有n+1个),那就要用主席树动态开点.而取第K大值的操作有点像超级钢琴,不过

POJ 3294 Life Forms(后缀数组求k个串的最长子串)

题目大意:给出n个字符串,让你求出最长的子串,如果有多个按照字典序顺序输出. 解题思路:将n个字符串连起来,中间需要隔开,然后我们二分枚举字符串的长度,求最长的长度,如果多个需要按照字典序保存起来,最后输出答案就可以了.时间复杂度是:O(n*log(n)). Life Forms Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 10275   Accepted: 2822 Description You may have

bzoj 4504: K个串【大根堆+主席树】

像超级钢琴一样把五元组放进大根堆,每次取一个出来拆开,(d,l,r,p,v)表示右端点为d,左端点区间为(l,r),最大区间和值为v左端点在p上 关于怎么快速求区间和,用可持久化线段树维护(主席树?)每个点到他root的区间和,这样每次右端点右移就是上一个的线段树在(la[a[i]]+1,i)加上a[i],la是这个值a[i]上一次出现的位置 然后就可以在线处理询问了 有一点因为这个线段树建的是1~n,所以右端点不是n的时候取max会取到右端点向右还是初始值0的位置(有可能前面是负数),这样的解

bzoj3473字符串&amp;bzoj3277串

题意:给定n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串.注意本质相同的子串多次出现算多次,如1 1 aaa这组数据答案为6,贡献1WA.代码里有些部分是为了处理子串本质不同,可能没删干净. 因为字符串的总长不超过10^5,那么后缀的个数也不超过10^5.一个长为x的后缀可以产生x个子串,其中在至少k个串中出现的子串一定是长度从1 开始递增的连续几个.那么对每个后缀可以二分找出最多能够产生多么长的在至少k个串中出现的子串.把所有串连起来求后缀数组,二分一