bzoj 4504: K个串 可持久化线段树+堆
题目:
Description
兔子们在玩k个串的游戏。首先,它们拿出了一个长度为n的数字序列,选出其中的一
个连续子串,然后统计其子串中所有数字之和(注意这里重复出现的数字只被统计一次)。
兔子们想知道,在这个数字序列所有连续的子串中,按照以上方式统计其所有数字之和,第
k大的和是多少。
Input
第一行,两个整数n和k,分别表示长度为n的数字序列和想要统计的第k大的和
接下里一行n个数a_i,表示这个数字序列
Output
一行一个整数,表示第k大的和
Sample Input
7 5
3 -2 1 2 2 1 3 -2
Sample Output
4
HINT
1 <= n <= 100000, 1 <= k <= 200000, 0 <= |a_i| <= 10^9数据保证存在第 k 大的和
题解:
首先我们发现,如果相同的字符的贡献可以重复计算的话,那就是超级钢琴了.
可以直接应用堆来求解
但是这道题中相同字符的贡献只统计一次,就需要我们瞎搞一下了。
对于这道题,我们可以通过可持久化线段树来处理去重的问题
怎么处理呢...
我们可以对每个点维护以这个点为右端点的所有子段和
这样我们考虑如何从i-1拓展到i
实际上我们拓展的时候只需要考虑第i位上的数字对前面做出的贡献
我们发现其实这个字符只对一部分左端点连续的字段做出贡献
具体一点,i位置上的数字只对左端点在\([last[a[i]]+1 ~ i]\)位置上的子段做出贡献
所以我们可以在可持久化线段树中通过区间修改实现\(nlogn\)处理出所有的子段
然后就是超级钢琴啦....
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(ll &x){
x=0;char ch;bool flag = false;
while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
const ll maxn = 210010;
struct Node{
Node *ch[2];
ll mx,id,lazy;
void update(){
mx = max(ch[0]->mx,ch[1]->mx);
if(mx == ch[0]->mx) id = ch[0]->id;
if(mx == ch[1]->mx) id = ch[1]->id;
mx += lazy;
}
}*null,*root[maxn],*it,mem[maxn*50];
inline void init(){
it = mem;null = it++;
null->ch[0] = null->ch[1] = null;
null->mx = null->id = 0;
root[0] = null;
}
Node* insert(Node *rt,ll l,ll r,ll L,ll R,ll val){
Node *p = it++;*p = *rt;
if(L == R && l == r){
p->id = l;
}
if(L <= l && r <= R){
p->lazy += val;
p->mx += val;
return p;
}
ll mid = l+r >> 1;
if(L <= mid) p->ch[0] = insert(p->ch[0],l,mid,L,R,val);
if(R > mid) p->ch[1] = insert(p->ch[1],mid+1,r,L,R,val);
p->update();return p;
}
typedef pair<ll,ll> pa;
inline pa query(Node *p,ll l,ll r,ll L,ll R){
if(L <= l && r <= R){
return make_pair(p->mx,p->id);
}
ll mid = l+r >> 1;pa x,y;
if(R <= mid){
x = query(p->ch[0],l,mid,L,R);
x.first += p->lazy;
return x;
}
if(L > mid){
y = query(p->ch[1],mid+1,r,L,R);
y.first += p->lazy;
return y;
}
x = query(p->ch[0],l,mid,L,R);
y = query(p->ch[1],mid+1,r,L,R);
x.first += p->lazy;y.first+=p->lazy;
if(x.first > y.first) return x;
else return y;
}
ll a[maxn],last[maxn],b[maxn],c[maxn];
struct num{
ll l,r;
ll val,le,ri;
bool friend operator < (const num &a,const num &b){
return a.val < b.val;
}
num(){}
num(const ll &a,const ll &b,const ll &c,const ll &d,const ll &e){
l = a;r = b;val = c;le = d;ri = e;
}
};
priority_queue<num>q;
int main(){
init();
ll n,k;read(n);read(k);
for(ll i=1;i<=n;++i){
read(a[i]);
b[i] = a[i];
}
sort(b+1,b+n+1);
for(ll i=1;i<=n;++i){
c[i] = lower_bound(b+1,b+n+1,a[i]) - b;
}
memset(last,-1,sizeof last);
for(ll i=1;i<=n;++i){
b[i] = last[c[i]];
last[c[i]] = i;
}
for(ll i=1;i<=n;++i){
root[i] = insert(root[i-1],1,n,i,i,a[i]);
if(b[i] != -1){
ll l = b[i] + 1,r = i-1;
if(l <= r){
root[i] = insert(root[i],1,n,l,r,a[i]);
}
}else{
ll l = 1,r = i-1;
if(l <= r) root[i] = insert(root[i],1,n,l,r,a[i]);
}
pa x = query(root[i],1,n,1,i);
q.push(num(x.second,i,x.first,1,i));
}
while(--k){
num x = q.top();q.pop();
ll l = x.l + 1,r = x.ri;
if(l <= r){
pa tmp = query(root[x.r],1,n,l,r);
q.push(num(tmp.second,x.r,tmp.first,l,r));
}
l = x.le;r = x.l - 1;
if(l <= r){
pa tmp = query(root[x.r],1,n,l,r);
q.push(num(tmp.second,x.r,tmp.first,l,r));
}
}
num ans = q.top();
printf("%lld\n",ans.val);
getchar();getchar();
return 0;
}
如果你不会超级钢琴...
这道题要求k大值,实际上我们可以考虑依次求出第一大、第二大、第三大、 ... 、第k大
那么问题就在于如何求解这个东西。
我们可以采用如下的策略:
将所有的以某个点为右端点的所有向左拓展出来的子段中的最大子段和扔到堆里
每次从堆中取出当前子段和最大的子段,然后将这个子段的左区间向左向右延伸得到的最大的两个子段再扔进堆里
取出来的第i的即为第i大的子段和.
bzoj 4504: K个串 可持久化线段树+堆的更多相关文章
- 【BZOJ4504】K个串 可持久化线段树+堆
[BZOJ4504]K个串 Description 兔子们在玩k个串的游戏.首先,它们拿出了一个长度为n的数字序列,选出其中的一个连续子串,然后统计其子串中所有数字之和(注意这里重复出现的数字只被统计 ...
- hihocoder#1046 K个串 可持久化线段树 + 堆
首先考虑二分,然后发现不可行.... 注意到\(k\)十分小,尝试从这里突破 首先用扫描线来处理出以每个节点为右端点的区间的权值和,用可持久化线段树存下来 在所有的右端点相同的区间中,挑一个权值最大的 ...
- bzoj : 4504: K个串 区间修改主席树
4504: K个串 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 268 Solved: 110[Submit][Status][Discuss] ...
- [BZOJ 3123] [SDOI 2013]森林(可持久化线段树+并查集+启发式合并)
[BZOJ 3123] [SDOI 2013]森林(可持久化线段树+启发式合并) 题面 给出一个n个节点m条边的森林,每个节点都有一个权值.有两种操作: Q x y k查询点x到点y路径上所有的权值中 ...
- bzoj 4504: K个串【大根堆+主席树】
像超级钢琴一样把五元组放进大根堆,每次取一个出来拆开,(d,l,r,p,v)表示右端点为d,左端点区间为(l,r),最大区间和值为v左端点在p上 关于怎么快速求区间和,用可持久化线段树维护(主席树?) ...
- SPOJ-COT-Count on a tree(树上路径第K小,可持久化线段树)
题意: 求树上A,B两点路径上第K小的数 分析: 同样是可持久化线段树,只是这一次我们用它来维护树上的信息. 我们之前已经知道,可持久化线段树实际上是维护的一个前缀和,而前缀和不一定要出现在一个线性表 ...
- BZOJ 3653: 谈笑风生(DFS序+可持久化线段树)
首先嘛,还是太弱了,想了好久QAQ 然后,这道题么,明显就是求sigma(size[x]) (x是y的儿子且层树小于k) 然后就可以发现:把前n个节点按深度建可持久化线段树,就能用前缀和维护了 其实不 ...
- BZOJ.2653.[国家集训队]middle(可持久化线段树 二分)
BZOJ 洛谷 求中位数除了\(sort\)还有什么方法?二分一个数\(x\),把\(<x\)的数全设成\(-1\),\(\geq x\)的数设成\(1\),判断序列和是否非负. 对于询问\(( ...
- bzoj 3514: GERALD07加强版 lct+可持久化线段树
题目大意: N个点M条边的无向图,询问保留图中编号在[l,r]的边的时候图中的联通块个数. 题解: 这道题考试的时候没想出来 于是便爆炸了 结果今天下午拿出昨天准备的题表准备做题的时候 题表里就有这题 ...
随机推荐
- 杭电OJ(HDU)-ACMSteps-Chapter Three-《FatMouse' Trade》《今年暑假不AC》《排名》《开门人和关门人》
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpc2luaV92Yw==/font/5a6L5L2T/fontsize/400/fill/I0JBQk ...
- 为什么是kafka(二)
回答几个网友提出的问题,不清楚的能够看上一篇内容. 1. kafka的删除策略应该怎么配置?为了提升性能.我是不是应该1小时删除一次消费过的数据. 全然能够依据磁盘大小配置.仅仅要磁盘足够用,全然不 ...
- Web前端开发规范【HTML/JavaScript/CSS】
前言 这是一份旨在增强团队的开发协作,提高代码质量和打造开发基石的编码风格规范,其中包含了 HTML, JavaScript 和 CSS/SCSS 这几个部分.我们知道,当一个团队开始指定并实行编码规 ...
- intellij idea pycharm phpstorm webstorm 这些编辑器像 sublime text 一样使用 ctrl d 来操作光标多选。
在Windows 和 Linux 快捷键是 Alt+J,在 Mac OS X 系统下快捷键是 Ctrl+G . 参考 https://blog.jetbrains.com/phpstorm/2014/ ...
- Bootstrap导航栏头部错位问题
代码: <section class="header"> <div class="container"> <div class=& ...
- cocos2dx的ui封装
cocos2dx里加载cocosudio导出的ui配置文件,在这之上封装了一下,封装核心类包括 UIManager,UILayer,UIOwner UIManager是所有ui总的管理类,代码如下: ...
- KVM虚拟化技术实战全过程
今天准备开始.................... centos安装-kvm 教程: http://www.linuxidc.com/Linux/2017-01/140007.htm http:// ...
- Android 常用的权限
添加WiFi以及访问网络的权限: <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE&q ...
- SAP basis 常用事物
1.创建一个新的用户 完成client创建和拷贝后,在开始正式工作之前,需要创建新的用户. 用这个用户进行工作.默认ddic和sap*用户不要用于实际的业务. 创建用户的过程很简单,只要以su01 ...
- 常用grads函数
GrADS的函数分两类, 一类是对格点/站点数据执行运算的,这一类我们姑且称之为分析函数; 另一类是脚本编程(gs)时使用的, 这后一类我们姑且称之为脚本函数. 第一类分析函数又分为格点分析和站点分析 ...