数对

Portal --> who knows qwq

Description

  给你一个正整数序列\(a\),要求统计这个序列中满足\(a_i,a_{i+1}...a_{j-1},a_{j}\)中的最大值\(>a_i*a_j\)的数对\((i,j)\)的数量

  答案对\(998244353\)取模

  数据范围:\(n<=10^5,a_i<=10^9\)

  

Solution

  失去分治意识qwq

  一开始想的是启发式合并但是发现自己在最后的处理上面莫名脑抽然后快乐凉凉qwq

  

  这里两种方法都讲一下(复杂度都是\(O(nlog^2n)\)),首先是直接分治的方法:

  直接分治的话,我们考虑对于每一个区间\([l,r]\)只统计跨过中点\(mid\)的区间的贡献

  那么我们可以将\([l,mid]\)这个区间中的每一个数到\(mid\)的最大值预处理出来,将\([mid+1,r]\)这个区间中每一个数到\(mid\)的最大值预处理出来(都存在\(mx\)数组里面),然后先计算一遍\(a_l>=a_r\)的区间的贡献,再计算一遍\(a_l<a_r\)的区间的贡献即可

​  具体实现的话就是,因为是取\(max\),所以\(l\sim mid\)这段的\(mx\)是递减的,\(mid+1\sim r\)这段的\(mx\)是递增的,所以我们直接。。按照某个顺序枚举左端点,再将对应的\(mx\)满足条件的右端点的\(a\)值丢进一个数据结构里面,然后查询的时候查\(<=\)最大值\(/a_l\)\(a\)值即可,处理\(a_l<a_r\)的区间贡献的时候方式类似不再赘述

  至于这个数据结构的话。。懒得离散化什么的就用splay或者treap都可以的。。虽然说常数会巨大没错了qwq

  

​  然后第二种方法是按照最大值范围分治:

  这个名字有点抽象。。反正大概的意思就是说你可以处理出当前区间内的最大值(记这个位置为\(pos\)),然后的话最大值为\(a[pos]\)的区间的贡献可以这么计算:枚举\([l,pos]\)或者\([pos,r]\)中的每一个元素,然后查另外半边里面满足\(a_i<=a[pos]/\)枚举的那个数的\(i\)的数量

  然后关于这个枚举的话,类似启发式合并的思想,我们可以每次选择枚举较小的那半边来统计答案这样

​  同样要用到数据结构,可以选择先处理出修改操作然后离散化一下用树状数组来做,会跑得飞快​

  至于这种做法的复杂度为什么也是两个\(log\)的,其中一个\(log\)是数据结构,另外一个\(log\)的话,考虑我们每次分治:第一层分治我们分成\(2\)份,选择\(<=\frac{n}{2}\)的那份枚举(上界\(\frac{n}{2}\));第二层分治我们在原来份的\(2\)份中再各分\(2\)份(总共分成了\(4\)份),然后各选\(<=\)这份一半的那个枚举(总上界\(\frac{n}{4}\));以此类推。。。

  
​  mark:(没什么建设性的东西)分治是个好东西,左边对右边贡献什么的可以有十分优秀的复杂度qwq

  

  代码大概长这个样子

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<ctime>
using namespace std;
const int N=1e5+10,MOD=998244353;
int a[N],mx[N],lis[N];
int n,m,ans;
namespace Treap{/*{{{*/
	int ch[N][2],rk[N],val[N],sz[N],cnt[N];
	int rt,tot;
	void init(){rt=0; tot=0;}
	int newnode(int _val){
		ch[++tot][0]=0; ch[tot][1]=0;
		rk[tot]=rand(); val[tot]=_val; sz[tot]=1; cnt[tot]=1;
		return tot;
	}
	void pushup(int x){sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+cnt[x];}
	void rotate(int &x,int dir){
		int y=ch[x][dir];
		ch[x][dir]=ch[y][dir^1];
		ch[y][dir^1]=x;
		pushup(x);
		pushup(y);
		x=y;
	}
	void _insert(int &x,int delta){
		if (!x){
			x=newnode(delta); return;
		}
		++sz[x];
		if (delta==val[x]){
			++cnt[x]; return;
		}
		int dir=delta>val[x];
		_insert(ch[x][dir],delta);
		if (rk[ch[x][dir]]>rk[x])
			rotate(x,dir);
	}
	void insert(int delta){_insert(rt,delta);}
	int _query_smaller(int x,int delta){
		if (!x) return 0;
		if (val[x]==delta) return sz[ch[x][0]]+cnt[x];
		if (val[x]>delta) return _query_smaller(ch[x][0],delta);
		return _query_smaller(ch[x][1],delta)+sz[ch[x][0]]+cnt[x];
	}
	int query_smaller(int delta){return _query_smaller(rt,delta);}
}/*}}}*/
int plu(int x,int y){return (1LL*x+y)%MOD;}
int mul(int x,int y){return 1LL*x*y%MOD;}
void solve(int l,int r){
	int mid=l+r>>1,now;
	if (l==r){
		ans=plu(ans,(1LL*a[l]*a[l]==a[l])); 
		return;
	}
	mx[mid]=a[mid];
	for (int i=mid-1;i>=l;--i) mx[i]=max(mx[i+1],a[i]);
	for (int i=mid+1;i<=r;++i) mx[i]=max(mx[i-1],a[i]);

	Treap::init();
	now=mid+1;
	for (int i=mid;i>=l;--i){
		while (now<=r&&mx[now]<=mx[i])
			Treap::insert(a[now++]);
		ans=plu(ans,Treap::query_smaller(mx[i]/a[i]));
	}
	Treap::init();
	now=mid;
	for (int i=mid+1;i<=r;++i){
		while (now>=l&&mx[now]<mx[i])
			Treap::insert(a[now--]);
		ans=plu(ans,Treap::query_smaller(mx[i]/a[i]));
	}
	solve(l,mid);
	solve(mid+1,r);
}

int main(){
#ifndef ONLINE_JUDGE
	freopen("a.in","r",stdin);
#endif
	srand(time(0));
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%d",a+i);
	ans=0;
	solve(1,n);
	printf("%d\n",ans);
}
posted @ 2018-10-29 16:28  yoyoball  阅读(388)  评论(0编辑  收藏  举报