GYM 101102 J.Divisible Numbers(数论+容斥原理)

201 篇文章 10 订阅
190 篇文章 1 订阅

Description
给出一个长度为n的序列,q次查询,每次查询给出区间[l,r]和一个数s,s的二进制从右往左第i位表示i是否出现,统计[l,r]中有多少数可以被s所表示的这些出现的某一个数整除
Input
第一行一整数T表示用例组数,每组用例首先输入两整数n和q分别表示序列长度和查询数,之后n个数a[i]表示该序列,最后q行每行三个整数l,r,s分别表示查询的区间和1~10这十个数是否出现的二进制表示
(1<=n,q<=1e5,1<=a[i]<=1e9,1<=l<=r<=n,1<=s<=1023)
Output
对于每次查询,输出[l,r]中有多少数被出现的这些数中的某一个整除
Sample Input
1
4 2
2 5 3 8
1 3 2
2 4 355
Sample Output
1
3
Solution
枚举这1023种情况,求出每种情况出现的数字的最小公倍数,去重之后只有47种可能,num[i][j]表示a序列前i个数中有多少数可以被这47个数中的第j个整除,对于每次查询,假设出现的数是b[1]~b[res],如果b序列中某个数可以整除另一个,那么把大的数去掉,因为查询大的数没有意义,例如2 4同时出现,那么被4整除的数字一定被2整除,只需要统计被2整除的数字即可,4可以去掉,去掉这些情况后,用容斥定理计数即可,res最多为5,所以整体时间复杂度是O(47n+31q)
Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define maxn 100001
int T,n,q,a[666],c[2525],cnt,num[maxn][50],b[11];
int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}
int lcm(int a,int b)
{
    return a*b/gcd(a,b);
}
void init()
{
    cnt=0;
    for(int k=2;k<1024;k+=2)
    {
        int temp=1;
        for(int i=1;i<10;i++)
            if(k&(1<<i))temp=lcm(temp,i+1);
        a[cnt++]=temp;
    }
    sort(a,a+cnt);
    cnt=unique(a,a+cnt)-a;
    printf("cnt=%d\n",cnt); 
    for(int i=0;i<cnt;i++)c[a[i]]=i;
}
int main()
{
    init();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&q);
        memset(num[0],0,sizeof(num[0]));
        for(int i=1;i<=n;i++)
        {
            int temp;
            scanf("%d",&temp);
            for(int j=0;j<cnt;j++)
            {
                if(temp%a[j]!=0)num[i][j]=num[i-1][j];
                else num[i][j]=num[i-1][j]+1;
            }
        }
        while(q--)
        {
            int l,r,x,res=0;
            scanf("%d%d%d",&l,&r,&x);
            for(int i=0;i<10;i++)
                if(x&(1<<i))b[res++]=i+1;
            if(b[0]==1)printf("%d\n",r-l+1);
            else
            {
                for(int i=0;i<res;i++)
                    for(int j=i+1;j<res;j++)
                        if(b[j]%b[i]==0)
                            swap(b[j],b[res-1]),res--,j--;
                int N=1<<res,ans=0;
                for(int i=1;i<N;i++)
                {
                    int temp=1,count=0;
                    for(int j=0;j<res;j++)
                        if(i&(1<<j))temp=lcm(temp,b[j]),count++;
                    if(count&1)ans+=num[r][c[temp]]-num[l-1][c[temp]];
                    else ans-=num[r][c[temp]]-num[l-1][c[temp]];
                }
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值