整除【NOIP2016提高A组模拟9.21】

28 篇文章 0 订阅
17 篇文章 0 订阅

题目

麦克雷有一个1~n的排列,他想知道对于一些区间,有多少对区间内的数(x,y),满足x能被y整除
样例输入:
第一行包含2个正整数n,m。表示有n个数,m个询问。
接下来一行包含n个正整数,表示麦克雷有的数列。
接下来m行每行包含2个正整数l,r。表示询问区间[l,r]。
10 9
1 2 3 4 5 6 7 8 9 10
1 10
2 9
3 8
4 7
5 6
2 2
9 10
5 10
4 10

样例输出:
共 m 行,每行一个整数,表示满足条件的对数。
27
14
8
4
2
1
2
7
9

数据范围:
30%:1<=n,m<=100
100%:1<=n,m<=2*10^5,1<=pi<=n


剖解题目

。。。。。。


思路

区间问题,经过前面的轰炸后一眼就想莫队,然而却不会打。。。


解法

对于一个合法的(x,y),必须要求其两个都在区间[l,r]里才行,只要有一个不在,就是不合法的,我们只要将全部求出来,减去不合法的就行了。
对于y,我们把询问按照第二关键字升序排列,就可以解决。
每次加进一个数,a[i],判断其因数和倍数x是否在区间[1,r]里,在就把c[pos[x]]加一,pos[i]表示i在a数组中的位置,c[i]表示i这个位置可以对答案的贡献。
求和即可。

优化
一:很明显,c这个数组可以用数据结构优化,推荐BIT,segemen tree也行。
二:求n数的因数的时间复杂度是 nn 级别,但求其倍数却是 nlogn 级别,所以我们可以避开求因数,改为正向与反向各求一遍倍数即可。
优化一必须加。优化二看时间,2s内不加也行。


代码

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#define fo(i,a,b) for(int i=a;i<=b;i++)

using namespace std;

const int maxn=2*1e5+2;
int a[maxn],tree[maxn],pos[maxn];
int n,m;
bool bz[maxn];
struct cy{
    int l,r,id,ans;
}qs[maxn];


bool cmp1(cy a,cy b)
{
    return a.r<b.r;
}
bool cmp2(cy a,cy b)
{
    return a.id<b.id;
}
int lowbit(int x)
{
    return x&-x;
}
void add(int k,int x)
{
    while (k<=n){
        tree[k]+=x;
        k+=lowbit(k);
    }
}
int get(int k)
{
    int sum=0;
    while (k>0){
        sum+=tree[k];
        k-=lowbit(k);
    }
    return sum;
}
int main()
{
//  freopen("T.in","r",stdin);
    scanf("%d%d",&n,&m);
    fo(i,1,n) {
        scanf("%d",&a[i]);
        pos[a[i]]=i;
    }
    fo(i,1,m){
        scanf("%d%d",&qs[i].l,&qs[i].r);
        qs[i].id=i;
    }
    sort(qs+1,qs+m+1,cmp1);
    fo(i,1,m){
        fo(j,qs[i-1].r+1,qs[i].r){
            bz[a[j]]=true;
            fo(k,1,sqrt(a[j]))
            if (a[j]%k==0) {
                if (bz[k]) add(pos[k],1);
                if (bz[a[j]/k]&&a[j]/k!=k) add(pos[a[j]/k],1);
            }
            add(pos[a[j]],-1);
            fo(k,1,n/a[j]) 
            if (bz[k*a[j]]) add(pos[a[j]*k],1);
        }
        int x=get(qs[i].r),y=get(qs[i].l-1); 
        qs[i].ans=x-y;
    }
    sort(qs+1,qs+m+1,cmp2);
    fo(i,1,m) printf("%d\n",qs[i].ans);
}

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值