[bzoj P4504] K个串

[bzoj P4504] K个串

【题目描述】

兔子们在玩k个串的游戏。首先,它们拿出了一个长度为n的数字序列,选出其中的一个连续子串,然后统计其子串中所有数字之和(注意这里重复出现的数字只被统计一次)。

兔子们想知道,在这个数字序列所有连续的子串中,按照以上方式统计其所有数字之和,第k大的和是多少。

【输入格式】

第一行,两个整数n和k,分别表示长度为n的数字序列和想要统计的第k大的和

接下里一行n个数a_i,表示这个数字序列

【输出格式】

一行一个整数,表示第k大的和

【样例输入】

7 5

3 -2 1 2 2 1 3 -2

【样例输出】

4

【数据范围】

对于20%的数据,1 <= n <= 2000

对于另外20%的数据,0 <= a_i <= 10^9

对于100%的数据,1 <= n <= 100000, 1 <= k <= 200000, 0 <= |a_i| <= 10^9

数据保证存在第k大的和

题外话:好久都没有用博客园啦,最近写博客都写在WordPress上面。没想到今天炸了。。只好先来cnblogs避一避了。

对于这一题我也是一脸懵逼式的弃疗。连想都没怎么想,丝毫没有办法。

结果——主席树+堆。也是看了题解才明白的。自己怎么也想不到这种方法。

主思路是这样的,维护一个五元组(v,l,r,x,p)表示一个状态,分别表示当前状态所对应区间的和(v),左端点所在区间(l,r),左端点的具体位置(x),右端点的具体位置(p)。(这个思路骑士真的太难想到了,主要一个我觉得,习惯于考虑对称的东西,不会像这样,区间左右端点有别)

先不考虑如何如何构造或得到五元组。假设我们可以很快得到某一个特定的五元组。那如何得出第k大的和?

每一次从堆中取出最大值,也就是当前最大的和,然后做这样的事情:

构造五元组(maxsum_val(),l,p-1,maxsum_pos(),p)和(maxsum_val(),p+1,r,maxsum_pos(),p),并将它们push入堆中。显然这是正确的。

那刚开始在堆里的是什么呢?当然是p=1~n时,左端点在某一点能使区间和最大的这个状态咯。

这个问题——涉及到区间修改,区间查询。肯定要用线段树咯。但是我们发现用线段树是无法处理对于不同的右端点p,查询某个区间最值的问题的。

所以我们对于每一种右端点p,建立一颗主席树,然后在对应的主席树上高即可。

code:

 1 #pragma GCC optimize(2)
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <map>
 6 #include <queue>
 7 #define LL long long
 8 #define mp make_pair
 9 #define pli pair <LL,int>
10
11 using namespace std;
12
13 const int N=100005,M=10000005;
14
15 int n,m; map <int,int> pre;
16 struct node {
17     LL v; int x,l,r,p;
18     node () {}
19     node (LL _v,int _x,int _l,int _r,int _p) :
20         v(_v),x(_x),l(_l),r(_r),p(_p) {}
21     bool operator < (const node &o) const {
22         return v<o.v;
23     }
24 };
25 priority_queue <node> q;
26
27 namespace TREE {
28     int tot,rt[N],lc[M],rc[M]; pli w[M]; LL tag[M];
29     #define mid (((l)+(r))>>1)
30     #define ms(a,x) memset(a,x,sizeof a)
31     inline void init () {tot=0,ms(tag,0);}
32     inline void build (int &u,int l,int r) {
33         w[u=++tot]=mp(0,l);
34         if (l==r) return;
35         build(lc[u],l,mid),build(rc[u],mid+1,r);
36     }
37     inline void insert (int &u,int v,LL z) {
38         tag[u=++tot]=tag[v]+z;
39         lc[u]=lc[v],rc[u]=rc[v],w[u]=w[v],w[u].first+=z;
40     }
41     inline void upload (int u) {
42         w[u]=max(w[lc[u]],w[rc[u]]);
43     }
44     inline void download (int &u) {
45         insert(lc[u],lc[u],tag[u]),insert(rc[u],rc[u],tag[u]);
46         tag[u]=0;
47     }
48     inline void modify (int &u,int v,int l,int r,int x,int y,LL z) {
49         if (x<=l&&r<=y) {insert(u,v,z); return;}
50         if (tag[u]) download(v);
51         lc[u=++tot]=lc[v],rc[u]=rc[v],w[u]=w[v];
52         if (x<=mid) modify(lc[u],lc[v],l,mid,x,y,z);
53         if (y>mid) modify(rc[u],rc[v],mid+1,r,x,y,z);
54         upload(u);
55     }
56     inline pli query (int u,int l,int r,int x,int y,pli ret=mp(-1e18,0)) {
57         if (x<=l&&r<=y) return w[u];
58         if (tag[u]) download(u);
59         if (x<=mid) ret=query(lc[u],l,mid,x,y);
60         if (y>mid) ret=max(ret,query(rc[u],mid+1,r,x,y));
61         return ret;
62     }
63 } using namespace TREE;
64
65 inline void extend (int x,int l,int r) {
66     if (l>r) return;
67     pli nxt=query(x,1,n,l,r);
68     q.push(node(nxt.first,x,l,r,nxt.second));
69 }
70
71 int main () {
72     scanf("%d%d",&n,&m),init(),build(rt[0],1,n);
73     for (int i=1,x; i<=n; ++i) {
74         scanf("%d",&x);
75         modify(rt[i],rt[i-1],1,n,pre[x]+1,i,(LL)x);
76         extend(rt[i],1,i),pre[x]=i;
77     }
78     node cur;
79     for ( ; m; --m) {
80         cur=q.top(),q.pop();
81         extend(cur.x,cur.l,cur.p-1);
82         extend(cur.x,cur.p+1,cur.r);
83     }
84     printf("%lld\n",cur.v);
85     return 0;
86 }

原文地址:https://www.cnblogs.com/whc200305/p/8584871.html

时间: 2024-08-21 14:42:46

[bzoj P4504] K个串的相关文章

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的位置(有可能前面是负数),这样的解

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

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

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>

BZOJ 2342 回文串-Manacher

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2342 思路:先跑一遍Manacher求出p[i]为每个位置为中心的回文半径,因为双倍回文串的长度一定是4的倍数,即偶数,那么对于Manacher的回文中心一定是'#'字符.所以我们枚举每个'#',对于每个'#'当回文半径大于等于4才有可能成为双倍回文.如果当前位置的i是'#'且满足以上条件.那么我们就找到i右边的j.因为双倍回文的长度是4的倍数,那么i右边的j的回文长度一定是2的倍数,即

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大值的操作有点像超级钢琴,不过

BZOJ 2946 Poi2000 公共串 后缀自动机

题目大意:求n个串的最长公共子串 太久没写SAM了真是-- 将第一个串建成后缀自动机,用其它的串进去匹配 每个节点记录每个串在上面匹配的最大长度 那么这个节点对答案的贡献就是所有最大长度的最小值 对所有贡献取最大就行了= = 这最大最小看着真是别扭 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M 10100 using namesp

BZOJ 4503 两个串 ——FFT

[题目分析] 定义两个字符之间的距离为 (ai-bi)^2*ai*bi 如果能够匹配,从i到i+m的位置的和一定为0 但这和暴力没有什么区别. 发现把b字符串反过来就可以卷积用FFT了. 听说KMP+暴力可以卡到100ms以内(雾) [代码] #include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using na

BZOJ Zjoi2013 K大数查询

刚学了整体二分,跟随神犇的步伐走向了这道题...... 神犇:这道题不是二分答案裸题吗? 我:...... 也许是我真的太弱了吧: 不过好歹是A了,讲一讲我的思路: 首先,我们二分出一个答案mid,然后扫一遍当前区间内的询问,如果加入的数x>=mid,那么把这段区间的值都加1:这样就可以求出区间>=mid的数的个数了. 然后,根据这些东西判断一下当前询问该丢到左边还是右边,递归处理就可以了.还有不要忘了询问的是区间第k大,所以对于丢到左边的询问要先把贡献给算进去. 下面贴代码: 1 #incl