您的位置:首页 > 其它

HDU 4676 Sum Of Gcd(欧拉函数求区间gcd之和+分块算法)

2015-10-04 23:08 295 查看

HDU 4676 Sum Of Gcd

题意:

给一个数列a,以及一些询问[L,R] ,求每一个询问的 ∑L≤i<j≤Rgcd(i,j)

思路:

令S(L,R)=∑L≤i<j≤Rgcd(i,j)

例如,当 a[L:R]={4,5,8} 时,S(L,R)=1+1+4=6。

由于最大公约数本身定义,我们可以猜想多个数的gcd之和一定这些数约数个数有关。

比如说上例,其中4的约数有1,2,4;5的约数有1,5;8的约数有1,2,4,8.

令 num(d)= 约数d的出现次数。

那么 num(1)=3,num(2)=2,num(4)=2,num(5)=1,num(8)=1

猜想结论为:S(L,R)=∑d∗C2num(d)

这里的d表示所有 a[L:R] 的所有约数。

但经过上例验算,问题出在重复计数,比如4与8的公约数有1,2,4,但我们要的是最大公约数,上式把每一个约数都统计了。

所以要改为容斥计数:S(L,R)=∑f(d)∗C2num(d),其中,∑d|nf(d)=n

后面这个限制条件由来:对于一个统计过的数n,那么他的约数一定被统计过,为了避免重复统计,利用莫比乌斯的性质,对于n的所有约数的统计次数之和应等于n,f其实就是欧拉函数,于是 f(d)=φ(d),即 S(L,R)=∑φ(d)∗C2num(d)

由于直接暴力搞会TLE,于是用分块操作把查询离线处理:

往区间添加一个值n的时候,

∑d|nφ(d)∗C2num(d) 变为 ∑d|nφ(d)∗C2num(d)+1 ,

实际上相当于增加∑d|nφ(d)∗C1num(d)=∑d|nφ(d)∗num(d)

同理往区间删去一个值n的时候,相当于减少∑d|nφ(d)∗num(d)

时间复杂度: O(mn√+nn√)

代码:

/*
* @author FreeWifi_novicer
* language : C++/C
*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<string>
#include<map>
#include<set>
#include<vector>
#include<queue>

using namespace std;

#define clr( x , y ) memset(x,y,sizeof(x))
#define cls( x ) memset(x,0,sizeof(x))
#define pr( x ) cout << #x << " = " << x << endl
#define pri( x ) cout << #x << " = " << x << " "
#define test( t ) int t ; cin >> t ; int kase = 1 ; while( t-- )
#define out( kase ) printf( "Case #%d:\n" , kase++ )
#define mp make_pair
#define pii pair<int,int>
#define pb push_back
typedef long long lint;
typedef long long ll;
typedef long long LL;

const int maxn = 2 * 1e4 + 5 ;
int L , R , phi[maxn+5] , a[maxn+5] , num[maxn+5] ;
vector<int> d[maxn+5] ;
lint sum , ans[maxn+5];

struct Node{
int l , r , id , s ;
bool operator < ( const Node &a )const{
if( s != a.s ) return s < a.s ;
return r < a.r ;
}
}q[maxn+5] ;

void getPhi()
{
cls( phi ) ;
phi[1] = 1 ;
for( int i = 2 ; i < maxn ; i++ )
{
if( !phi[i] )
for( int j = i ; j < maxn ; j += i )
{
if( !phi[j] ) phi[j] = j ;
phi[j] = phi[j] / i * ( i - 1 ) ;
}
}
}

void getFac()
{
for( int i = 1 ; i < maxn ; i++ )
for( int j = i ; j < maxn ; j += i )
d[j].pb( i ) ;
}
void init()
{
getFac() ; getPhi() ;
}
void insert( int x )
{
for( int i = 0 ; i < (int)d[x].size() ; i++ )
sum += phi[d[x][i]] * ( num[d[x][i]]++ ) ;
}
void del( int x )
{
for( int i = 0 ; i < (int)d[x].size() ; i++ )
sum -= phi[d[x][i]] * ( --num[d[x][i]] ) ;
}
lint query( int l , int r , int x )
{
if( !x )
{
sum = 0 ;
for( int i = l ; i <= r ; i++ )
insert( a[i] ) ;
}
else
{
for( int i = l ; i < L ; i++ ) insert( a[i] ) ;
for( int i = R + 1 ; i <= r ; i++ ) insert( a[i] ) ;
for( int i = L ; i < l ; i++ )  del( a[i] ) ;
for( int i = r + 1 ; i <= R ; i++ )  del( a[i] ) ;
}
L = l ; R = r ;
return sum ;
}
int main(){
//freopen("input.txt","r",stdin);
init() ;
test( t )
{
cls( num ) ;
int n ; cin >> n ;
int magic = (int)sqrt( n + 0.5 ) ;
for( int i = 1 ; i <= n ; i++ )
scanf( "%d" , a + i ) ;
int m ; cin >> m ;
for( int i = 0 ; i < m ; i++ )
{
scanf( "%d%d" , &q[i].l , &q[i].r ) ;
q[i].id = i ; q[i].s = q[i].l / magic ;
}
sort( q , q + m ) ;
for( int i = 0 ; i < m ; i++ )
{
ans[q[i].id] = query( q[i].l , q[i].r , i ) ;
}
out( kase ) ;
for( int i = 0 ; i < m ; i++ )
{
printf( "%I64d\n" , ans[i] ) ;
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  gcd 数论 分块