zoj3800线段树+离线

本文介绍了一种解决特定区间公因子查询问题的高效算法。该算法利用线段树结构存储和查询区间内最大公约数(GCD),适用于多个查询场景。通过对输入数据的预处理和动态更新线段树,实现快速响应查询需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

You are given a sequence {A0, A1, …, AN-1} and an integer set called GS. Defined a function called G(L, R) on A, where G(L, R) = GCD(Ai) (L ≤ i < R) that is the greatest common divisor of all the integers in the subsequence {AL, AL+1, …, AR-1}.

Now There’re several questions for you. Give you three integers L, R and g, where g is an integer in GS. You need to calculate how many integer pairs (l, r) satisfy the condition that G(l, r) = g and L ≤ l < r ≤ R.

Input
Input will consist of multiple test cases. The first line of each case contains three integers N, M, Q (1≤ N, Q≤ 100000, 1 ≤ M ≤ 50), separated by spaces. The second line contains N integers, A0, A1, …, AN-1 (1≤ Ai≤ 100000). The third line contains M integers, indicating the set GS. Every integer in GS is positive and less than 2000. For the next Q line, each line contains three integers, L, R, g (0≤ L< R≤ N, g∈ GS).

Output
For each case, output Q lines. Each line contains an integer, indicating the answer for the query.

Sample Input
4 4 4
1 2 3 4
1 2 3 4
0 4 1
0 4 2
0 4 3
0 4 4
Sample Output
7
1
1
1

思路:
我们可以分析,每一次查询的要素有三个分别是区间左端点、右端点和公因子。
1,由于集合中只有最多50个要查询的公因子,所以可以按照公因子的值分别建立线段树进行查询,这样就解决了公因子;
2,对于每一次查询我们可以固定右端点,将每一次查询放到对应的右端点上去,然后从左往右依次求公因子,更新公因子对应的线段树的值,至于查询我们是固定了右端点的,所以后面的数并不会影响结果,也就是现在就可以查询区间右端点和目前考虑到的右端点相同的区间了。这样就解决了区间端点问题;
3,从左往右由于考虑的数越来越多,求得的公因子的值肯定是非递增的(当出现一个新的端点时候,可能会出现一个新的公因子,也就是新出现的数本身),其中还可能会有重复的值,这样就可以去重减小复杂度了;

#include <bits/stdc++.h>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int maxn=100005;
int n,m,q,a[maxn],gs[53],rev[maxn];
long long ans[maxn];
struct node
{
    int L,g,i;
    node(int a=0,int b=0,int c=0)
    {
        L=a,g=b,i=c;
    }
};
vector<node> que[maxn];
struct segtree
{
    int add[maxn<<2];
    long long sum[maxn<<2];
    void build(int l,int r,int rt)
    {
        add[rt]=0;
        sum[rt]=0;
        if(l==r) return;
        int m=l+r>>1;
        build(lson);
        build(rson);
    }
    void pushUp(int rt)
    {
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    void pushDown(int rt,int l,int r)
    {
        if(add[rt])
        {
            int m=l+r>>1;
            sum[rt<<1]+=(long long)add[rt]*(m-l+1);
            sum[rt<<1|1]+=(long long)add[rt]*(r-m);
            add[rt<<1]+=add[rt];
            add[rt<<1|1]+=add[rt];
            add[rt]=0;
        }
    }
    void update(int L,int R,int l,int r,int rt)
    {
        if(L<=l&&r<=R)
        {
            add[rt]++;
            sum[rt]+=(r-l+1);
            return;
        }
        int m=l+r>>1;
        pushDown(rt,l,r);
        if(L<=m)
            update(L,R,lson);
        if(m<R)
            update(L,R,rson);
        pushUp(rt);
    }
    long long query(int L,int R,int l,int r,int rt)
    {
        if(L<=l&&r<=R)
            return sum[rt];
        pushDown(rt,l,r);
        int m=l+r>>1;
        long long ans=0;
        if(L<=m) ans+=query(L,R,lson);
        if(m<R) ans+=query(L,R,rson);
        return ans;
    }
} tree[51];
int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}
void solve()
{
    int tot=0;
    pair<int,int> sp[maxn];
    for(int i=1; i<=n; i++)
    {
        for(int j=0; j<tot; j++)
        {
            int tmp=gcd(sp[j].first,a[i]);
            sp[j].first=tmp;
        }
        if(tot>0&&a[i]==sp[tot-1].first)
            sp[tot-1].second++;
        else
            sp[tot++]=make_pair(a[i],1);
        if(tot)
        {
            int tmp=1;
            for(int j=1; j<tot; j++)
            {
                if(sp[j].first==sp[tmp-1].first)
                    sp[tmp-1].second+=sp[j].second;
                else
                    sp[tmp++]=sp[j];
            }
            tot=tmp;
        }

        int cnt=0,L,R,tmp,pos;
        for(int j=0; j<tot; j++)
        {
          tmp=sp[j].first;
          pos=rev[tmp];
          if(pos!=-1)
          {
              L=cnt+1;
              R=cnt+sp[j].second;
              tree[pos].update(L,R,1,n,1);
          }
          cnt+=sp[j].second;
        }
        for(int j=0; j<que[i].size(); j++)
        {
            node tt=que[i][j];
            ans[tt.i]=tree[rev[tt.g]].query(tt.L,i,1,n,1);
        }
    }
}
int main()
{
    while(~scanf("%d%d%d",&n,&m,&q))
    {
        memset(rev,0xff,sizeof rev);
        for(int i=0; i<=m; i++)
            tree[i].build(1,n,1);
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            que[i].clear();
        }
        for(int i=1; i<=m; i++)
        {
            scanf("%d",&gs[i]);
            rev[gs[i]]=i;
        }
        int L,R,g;
        for(int i=1; i<=q; i++)
        {
            scanf("%d%d%d",&L,&R,&g);
            L++;
            que[R].push_back(node(L,g,i));
        }
        solve();
        for(int i=1; i<=q; i++)
            printf("%lld\n",ans[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值