hdu5869 离线BIT+ST+RMQ

//题意:1e5区间查询【l,r】内的所有子区间有多少种不同的gcd值

//思路:离线BIT

//套路:问区间内所有子区间中有多少个数:从右端点小到大处理查询,维护值最近出现的下标 
//对每个固定的右端点二分+RMQ维护向左滑动的  lft累计gcd与最右下标 gcd特性:lft大小nlogn 
//这样我们离线处理的时候可以暴力遍历lft对每个gcd值转移到最右的位置 (这样保证每个gcd只算一次,而且不会漏解) 
//查询是查位置l到r上的数字个数和 
//修改是修改gcd值出现的位置
//比如6 3 8 3 3 6 考虑3这个数的位置变化 
//查询
//1 2
//4 5
//1 5 
//1 6 
//查询的右端点为2时,将vis[gcd(a1,a2)=3]初始化到位置2
//查询的右端点为4时,将vis[gcd(a4,a4)=3]转移到位置4
//查询的右端点为5时,将vis[gcd(a4,a5)=3]转移到位置5
//查询的右端点为6时,将vis[gcd(a4,a5)=3]还是位置5
//这样 当右端点为5时我查询4 5,1 5就都不会漏解了(查询1 5 子区间gcd(a1,a2)的贡献放在了子区间gcd(a4,a5)上) 

//代码思路:
//A 预处理ST 
//B 预处理lft
//C BIT离线处理 
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int vis[1000006]; int c[maxn];int n;//手抖写成bool类型 
inline int lowbit(int x){return x&(-x);}
inline void add(int x,int d){if(x==0)return; while(x<=n)c[x]+=d,x+=lowbit(x);}
inline int ask(int x){int res=0;while(x)res+=c[x],x-=lowbit(x);return res;}

int dp[maxn][20];int a[maxn];int logg[maxn];
vector<pair<int,int> >lft[maxn];

struct node{int l,r,id;}q[maxn];
bool cmp(node a,node b){return a.r<b.r;}
int res[maxn];
void ST(int n)//区间GCD 
{
	for(int i=1;i<=n;i++)dp[i][0]=a[i],logg[i]=(int)log2(i);
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
		{
			int x=dp[i][j-1],y=dp[i+(1<<j-1)][j-1];
			dp[i][j]=__gcd(x,y);
		}
}
int RMQ(int l,int r)
{
	int len=logg[r-l+1];
	int x=dp[l][len],y=dp[r-(1<<len)+1][len];
	return __gcd(x,y);
}
//二分 找转折点 
int sch(int i,int l,int r,int qq)
{
	int tem=0;
	while(l<=r)
	{
		int m=l+r>>1;
		if(RMQ(m,i)<qq)l=m+1;
		else r=m-1,tem=m;
	}
	return tem;
}
void init(int n)//预处理lft 
{
	for(int i=1;i<=n;i++)
	{
		lft[i].clear();
		int la=i;int qq=RMQ(i,i);int nxt=qq;
		lft[i].push_back(make_pair(i,qq));
		while(1)
		{
			int ans=sch(i,1,la,nxt);
			if(ans==1)break;
			nxt=RMQ(ans-1,i);
			lft[i].push_back(make_pair(ans-1,nxt));
			la=ans-1;
		}
	}
}
int main()
{
	int qqq;
	while(~scanf("%d %d",&n,&qqq))
	{
		memset(vis,0,sizeof(vis));memset(c,0,sizeof(c));
		for(int i=1;i<=n;i++)scanf("%d",&a[i]);
		ST(n);init(n);
	//	for(int i=0;i<lft[1].size();i++)printf("%d %d\n",lft[1][i].first,lft[1][i].second);
		for(int i=1;i<=qqq;i++)scanf("%d %d",&q[i].l,&q[i].r),q[i].id=i;
		sort(q+1,q+1+qqq,cmp);
		int r=1;
		for(int i=1;i<=qqq;i++)
		{
			for(;r<=q[i].r;r++)
				for(int j=0;j<lft[r].size();j++)
				{
					int pos=lft[r][j].first,gcd=lft[r][j].second;
					//printf("pos=%d %d %d %d\n",r,pos,gcd,vis[gcd]);
					if(pos>vis[gcd])
					{
						
						add(vis[gcd],-1);
						add(pos,1);
						vis[gcd]=pos;
						//cout<<"???"<<endl;
					}
				}
				//printf("q:%d %d %d %d\n",ask(q[i].r),ask(q[i].l-1),q[i].r,q[i].l);
			res[q[i].id]=ask(q[i].r)-ask(q[i].l-1);
		}
		for(int i=1;i<=qqq;i++)printf("%d\n",res[i]);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值