HDU 5726 GCD(线段树+预处理)

本文介绍了一种使用线段树高效解决区间GCD查询及统计相同GCD区间数量的方法。通过固定右端点并逐步移动左端点,利用序列非增特性将问题简化,最终实现log级别复杂度。

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

[题目链接]

[题意]
给定数列an,每次询问区间[l,r]上所有数的gcd,并求gcd为此值的区间有多少个?

[分析]
区间gcd用线段树很好维护
关键在于如何求gcd为某值的区间个数
我们固定右端点r,左端点从1移动到r,求得一个gcd序列
容易发现,这个序列是非增的,且可以分为许多段数值相同的区间段
可以证明,这样的区间段个数不超过log2(n),因为每次gcd值下降时至少降为原来的一半
把右端点右移一位为r+1,用新加入的数a[r+1]去更新所有的区间段的gcd值,再合并gcd值相同的区间段
在这个过程中,区间段的个数始终是log级别的
最后用map统计个数即可

[代码]

#include <bits/stdc++.h>
using namespace std ;
const int N = 1e5 ;
typedef long long LL ;

int T , Case , n , q , a[N] ;
int g[N*4] ;

#define lson l,m,o<<1
#define rson m+1,r,o<<1|1
#define root 1,n,1

void build( int l , int r , int o )
{
    if( l == r )
    {
        g[o] = a[l] ;
        return ;
    }
    int m = (l+r)>>1 ;
    build(lson) ;
    build(rson) ;
    g[o] = __gcd(g[o<<1],g[o<<1|1]) ;
}

int query( int L , int R , int l , int r , int o )
{
    if( L <= l && R >= r )
        return g[o] ;
    int m = (l+r)>>1 ;
    if( L > m ) return query(L,R,rson) ;
    if( R <= m ) return query(L,R,lson) ;
    return __gcd(query(L,R,lson),query(L,R,rson)) ;
}

struct seg
{
    int p , g ;
    seg() { }
    seg( int p , int g ):p(p),g(g) { }
    bool operator == ( const seg &rhs ) const
    {
        return g == rhs.g ;
    }
} s[22] ;

map<int,LL> cnt ;

void cal()
{
    cnt.clear() ;
    int k = 0 ;
    for( int i = 1 ; i <= n ; i++ )
    {
        s[k++] = seg(i,a[i]) ;
        for( int j = k-2 ; j >= 0 ; j-- )
            s[j].g = __gcd(s[j].g,s[j+1].g) ;
        k = unique(s,s+k) - s ;
        for( int j = 0 ; j < k-1 ; j++ )
            cnt[s[j].g] += s[j+1].p - s[j].p ;
        cnt[s[k-1].g] += i+1-s[k-1].p ;
    }
}

int main()
{
    scanf( "%d" , &T ) ;
    while( T-- )
    {
        scanf( "%d" , &n ) ;
        for( int i = 1 ; i <= n ; i++ )
            scanf( "%d" , &a[i] ) ;
        build(root) ;
        cal() ;
        scanf( "%d" , &q ) ;
        printf( "Case #%d:\n" , ++Case ) ;
        while( q-- )
        {
            int l , r ;
            scanf( "%d%d" , &l , &r ) ;
            int gcd = query(l,r,root) ;
            printf( "%d %I64d\n" , gcd , cnt[gcd] ) ;
        }
    }
    return 0 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值