牛客练习赛70 F.曲调(离线,思维,权值线段树)

Posted issue是fw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了牛客练习赛70 F.曲调(离线,思维,权值线段树)相关的知识,希望对你有一定的参考价值。

LINK

不看题解完全想不到,写下思路缕一缕 (顺便福利以下大众)


对数组 a a a做前缀和,那么每次只需要找到 [ l , r ] [l,r] [l,r]内差值最小的两个数即可

但是这个也显然做不了,考虑对询问区间排序后按照 r r r端点离线

顺序扫描每个 i i i,考虑 a i a_i ai和前面数字 a q a_q aq形成的贡献

显然可以选择找 a i a_i ai最接近的 a q a_q aq,然而此时 q q q可能很小,很多区间更新不到

所以为了不遗漏任何答案,我们先找到最大的 k k k满足 k < i & & a k < = a i k<i\\&\\&a_k<=a_i k<i&&ak<=ai

显然,右端点大于等于 i i i且左端点在 k k k左边的区间都可以更新答案

然后我们再从 k k k往左边扫描,也就是找到一个最大的 k 1 < k & & a i > = a k 1 > a k k_1<k\\&\\&a_i>=a_{k_1}>a_k k1<k&&ai>=ak1>ak

因为这样虽然能更新的区间不如上一个多,但是更新的值却变为更优的 a b s ( a k 1 − a i ) abs(a_{k_1}-a_i) abs(ak1ai)

然后一直重复这个步骤去更新区间即可

看上去完全没有优化,因为每次扫描的复杂度为 O ( n ) O(n) O(n)

然而我们发现,第一次我们找到的是索引最大的 k k k满足 a k < a i a_k<a_i ak<ai

第二次找到的是最大索引的 k 1 k_1 k1满足 a k < a k 1 < a i a_k<a_{k_1}<a_i ak<ak1<ai

这个 k 1 k_1 k1的贡献是 a b s ( a i − a k 1 ) abs(a_i-a_{k_1}) abs(aiak1),而 a k , a k 1 a_k,a_{k_1} ak,ak1的贡献是 a b s ( a k 1 − a k ) abs(a_{k_1}-a_k) abs(ak1ak)

显然后者更优,而且能覆盖的区间范围也更广,所以我们的 k 1 k_1 k1不是乱选的,至少需要满足

a i + a k 2 + 1 < = a k 1 < = a i \\frac{a_i+a_k}{2}+1<=a_{k_1}<=a_i 2ai+ak+1<=ak1<=ai

于是发现 a k 1 a_{k_1} ak1的值域减小的非常快,似乎是 l o g log log次的

所以现在的任务就是从 a [ 1... i − 1 ] a[1...i-1] a[1...i1] a q ∈ [ a i + a k 2 , a i ] a_q\\in[\\frac{a_i+a_k}{2},a_i] aq[2ai+ak,ai]的最大索引 q q q

这个上个动态开点权值线段树即可,区间维护最大索引,动态插入 a i a_i ai即可

接下来就是更新左端点小于等于 k k k的所有区间,这只需要一个区间 m i n min min,同样上线段树

然后就在 O ( n l o g ( n ) l o g ( w ) ) O(nlog(n)log(w)) O(nlog(n)log(w))的复杂度做完了, w w w是前缀和值域


细节

Ⅰ.由于是做前缀和,需要把区间的左端点减一

Ⅱ.权值线段树下标不能是负数,为了不离散化,直接对每个前缀和加上 1 0 15 10^{15} 1015

Ⅲ.缩小值域的时候,注意取整问题

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid (l+r>>1)
#define lson ls,l,mid
#define rson rs,mid+1,r
const int inf = 2e15;
const int maxn = 3e5+10;
const int N = 2e6+10;
int n,m,ans[maxn],b[maxn];
struct p
{
	int l,r,id;
	bool operator < ( const p&tmp )	const { return r<tmp.r; }
}a[maxn];

int mx[N],LS[N],RS[N],id,root;
void insert(int &rt,int l,int r,int pos,int val)//单点更新 
{
	if( !rt )	rt = ++id;
	mx[rt] = max( mx[rt],val );//因为val是单调递增的 
	if( l==r )	return;
	if( pos<=mid )	insert( LS[rt],l,mid,pos,val ); 
	else	insert( RS[rt],mid+1,r,pos,val );
}
int ask(int rt,int l,int r,int L,int R)//查询[L,R]内序号最大的
{
	if( !rt || l>R || r<L )	return -1;
	if( l>=L && r<=R )	return mx[rt];
	return max( ask(LS[rt],l,mid,L,R),ask(RS以上是关于牛客练习赛70 F.曲调(离线,思维,权值线段树)的主要内容,如果未能解决你的问题,请参考以下文章

GYM 101350 F. Monkeying Around(线段树 or 思维)

Codeforces1539 F. Strange Array(思维,线段树)

Educational Codeforces Round 47 (Rated for Div. 2)F. Dominant Indices 线段树合并

牛客练习赛85 B 音乐家的曲调(尺取法)

牛客练习赛D数学家的谜题线段树+bitset优化

牛客练习赛100E.小红的公倍数(线段树+究极卡常