Jzoj4792 整除

227 篇文章 3 订阅
134 篇文章 0 订阅

给你一个1~n的排列A,询问一些区间内有多少对x,y使得A[x]|A[y]

我们考虑将所有的询问按照r排序,让后分成两部分计算:

1.A[i]|A[j] 且i>j

2.A[i]|A[j] 且i<j

这里只考虑第一种,第二种将所有询问对称翻转即可

我们建立一个树状数组,我们令i=1~n

每次将考虑A[i]的倍数,若kA[i]的位置j在i之前,我们就在树状数组中把s[j]+1

那么显然,对于一个询问,若其右端点为i,那么显然这个询问的答案就是树状数组中[l,i]的和

这样就完成了一部分的计算,另一部分可以翻转后按照同样的方法计算

最后答案记得加上r-l+1(区间长度)

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 200020
using namespace std;
inline int c(int x){ return x&-x; }
struct E{ int l,r,k; } s[N];
inline bool c1(E a,E b){ return a.r<b.r; }
int n,m,t=0,b[N],d[N],A[N],v[N];
inline void sp(int& a,int& b){ a^=b; b^=a; a^=b; }
inline void add(int x){ for(;x<=n;x+=c(x)) b[x]++; }
inline int sum(int x,int k=0){ for(;x;x^=c(x)) k+=b[x]; return k; }
void KM(){
	memset(b,0,sizeof b);
	for(int i=1,j;i<=m;++i){
		for(j=s[i-1].r+1;j<=s[i].r;++j)
			for(int k=2,z=n/d[j];k<=z;++k)
				if(v[k*d[j]]<j) add(v[k*d[j]]);
		A[s[i].k]+=sum(s[i].r)-sum(s[i].l-1);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",d+i),v[d[i]]=i;
	for(int i=1;i<=m;s[i].k=i,++i) scanf("%d%d",&s[i].l,&s[i].r),A[i]=s[i].r-s[i].l+1;
	sort(s+1,s+1+m,c1); KM();
	reverse(d+1,d+1+n);
	for(int i=1;i<=n;++i) v[i]=n-v[i]+1;
	for(int i=1;i<=m;++i) { 
		sp(s[i].l,s[i].r);
		s[i].l=n-s[i].l+1;
		s[i].r=n-s[i].r+1;
	}
	sort(s+1,s+1+m,c1); KM();
	for(int i=1;i<=m;++i) printf("%d\n",A[i]);
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值