1. 程式人生 > >HDU 5726 GCD(線段樹+預處理)

HDU 5726 GCD(線段樹+預處理)

[題意]
給定數列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 ; }