1. 程式人生 > 實用技巧 >冪次序列 雜湊+啟發式合併

冪次序列 雜湊+啟發式合併

冪次序列 雜湊+啟發式合併

題目描述

傳送門

分析

我們先不考慮精度問題

暴力的思想是對於每一個點\(i\),向前找是否存在一個點\(j\),使得\(sum[i]-sum[j-1]=2^k\)

我們考慮優化這個暴力

對於一段長度為\(k\)的區間,我們可以找到這個區間中的最大的數\(a[i]\)

而區間的和的指數一定不會超過\(2^{log_k+a[i]}\)

因此我們可以把這個區間分成兩半,一半在最大的數的左邊,另一半在最大的數的右邊

我們用指標在長度較短的區間從左到右掃一遍,同時在長度較長的區間的雜湊表中找和較小的部分拼起來的和恰好為\(2\)的冪的值的個數

因為字首和太大無法直接存,因此我們取字首和對一個質數取模後的結果

程式碼

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>
inline int read(){
	int x=0,fh=1;
	char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const long long mod=1926081719491001LL;
//社會主義加成
const int maxn=1e6+5;
std::vector<int> g[maxn];
struct m_hash{
	static const int Mod=261807;
	struct asd{
		int next;
		long long val;
	}b[maxn];
	int head[maxn],tot;
	m_hash(){
		memset(head,-1,sizeof(head));
		tot=1;
	}
	void ad(long long val){
		int now=1LL*val%Mod;
		for(int i=head[now];i!=-1;i=b[i].next){
			long long u=b[i].val;
			if(u==val) return;
		}
		b[tot].val=val;
		b[tot].next=head[now];
		head[now]=tot++;
	}
	int cx(long long val){
		int now=1LL*val%Mod;
		for(int i=head[now];i!=-1;i=b[i].next){
			long long u=b[i].val;
			if(u==val) return i;
		}
		return 0;
	}
}mp;
//雜湊表
int n,a[maxn];
long long sum[maxn];
long long gsc(long long aa,long long bb){
	long long z=(long double)aa/mod*bb;
	long long ans=(unsigned long long)aa*bb-(unsigned long long)z*mod;
	return (ans+mod)%mod;
}
//O(1)光速乘
long long ksm(long long ds,long long zs){
	long long ans=1;
	while(zs){
		if(zs&1LL) ans=gsc(ans,ds)%mod;
		ds=gsc(ds,ds)%mod;
		zs>>=1LL;
	}
	return ans;
}
//快速冪
int wz[maxn][22];
int get_val(long long val,int l,int r){
	if(!mp.cx(val)) return 0;
	int id=mp.cx(val);
	return std::upper_bound(g[id].begin(),g[id].end(),r)-std::lower_bound(g[id].begin(),g[id].end(),l);
}
//取在區間[l,r]中值恰好為val的區間個數
int get_wz(int l,int r){
	int k=log2(r-l+1);
	if(a[wz[l][k]]<a[wz[r-(1<<k)+1][k]]) return wz[r-(1<<k)+1][k];
	else return wz[l][k];
}
//找到區間最大值在哪裡
int ans=0;
void solve(int l,int r){
	if(l>r) return;
	if(l==r){
		ans++;
		return;
	}
	int mids=get_wz(l,r);
	solve(l,mids-1);
	solve(mids+1,r);
	long long max_val=ksm(2,1LL*a[mids]);
	int zqj=mids-l,yqj=r-mids;
	for(int cs=1;cs<=20;cs++,max_val=max_val*2%mod){
		if(zqj<yqj){
			for(int i=l;i<=mids;i++){
				ans+=get_val((max_val+sum[i-1])%mod,mids,r);
			}
		} else {
			for(int i=mids;i<=r;i++){
				ans+=get_val((sum[i]-max_val+mod)%mod,l-1,mids-1);
			}
		}
	}
}
//啟發式合併
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		wz[i][0]=i;
	}
	mp.ad(0);
	g[mp.tot-1].push_back(0);
	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1]+ksm(2,1LL*a[i]);
		sum[i]%=mod;
		if(mp.cx(sum[i])) g[mp.cx(sum[i])].push_back(i);
		else {
			mp.ad(sum[i]);
			g[mp.tot-1].push_back(i);
		}
	}
	for(int j=1;j<=20;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			if(a[wz[i][j-1]]<a[wz[i+(1<<(j-1))][j-1]]) wz[i][j]=wz[i+(1<<(j-1))][j-1];
			else wz[i][j]=wz[i][j-1];
		}
	}
	solve(1,n);
	printf("%d\n",ans);
	return 0;
}