HDU 5726 GCD(线段树+预处理)
2016-07-19 22:34
211 查看
[题目链接]
[题意]
给定数列an,每次询问区间[l,r]上所有数的gcd,并求gcd为此值的区间有多少个?
[分析]
区间gcd用线段树很好维护
关键在于如何求gcd为某值的区间个数
我们固定右端点r,左端点从1移动到r,求得一个gcd序列
容易发现,这个序列是非增的,且可以分为许多段数值相同的区间段
可以证明,这样的区间段个数不超过log2(n),因为每次gcd值下降时至少降为原来的一半
把右端点右移一位为r+1,用新加入的数a[r+1]去更新所有的区间段的gcd值,再合并gcd值相同的区间段
在这个过程中,区间段的个数始终是log级别的
最后用map统计个数即可
[代码]
[题意]
给定数列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 ; 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 ; }