您的位置:首页 > 其它

HDU 5381 The sum of gcd 离线处理+线段树

2015-08-24 10:56 405 查看

题意:给一组序列,多次询问,每次询问f(l,r)=∑ri=l∑rj=igcd(ai,ai+1....aj)

最初在赛场上看到这道题的时候,就想离线处理,但是怎么也不会。原因是当时并不知道这题的一个性质:从一个下标为i的数向后的连续区间[i,i+1]、[i,i+2]...[i,n]中不同的gcd(ai,ai+1,...,aj)最多只有log(ai)个。

有了这个性质我们可以干啥呢,对于每一个左端点对答案的贡献就可以就能求出来;那么我们把询问按照左端点排序,从大到小回答询问,移动左端点时,用线段树来更新答案,遇到一组询问就把询问的答案记下,最后输出即可。

至于怎么更新答案,假设我们已经知道ai+1的每一个gcd对应的连续区间,那么我们用ai与这些gcd做一次求gcd,得到一些单调不增的gcd,那么我们把相同的gcd对应的区间合并,在线段树区间修改里加上对应的gcd就行。

时间复杂度O(nlog2n)

P.S.可以直接用树状数组维护,但是线段树无脑一些,所以直接用线段树做了。

145194772015-08-1511:23:10Accepted5381171MS2472K2659BG++Kurama
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <map>
#include <algorithm>
#define MAXN 10001
using namespace std;
struct tree{int l,r;long long mark,val;}t[MAXN*4];
//线段树
struct query{int l,r,id;}q[MAXN];
//询问
bool cmp(const query &a,const query &b){return a.l>b.l;}
int num[MAXN],g[MAXN],l[MAXN],r[MAXN];
long long ans[MAXN];
int res;
void built(int left,int right,int pos)
{
t[pos].l=left,t[pos].r=right,t[pos].mark=0;
if(left==right){t[pos].val=0;return;}
int m=(left+right)>>1;
built(left,m,2*pos);
built(m+1,right,2*pos+1);
t[pos].val=t[2*pos].val+t[2*pos+1].val;
}
//建树
void check(int pos)
{
if(t[pos].mark!=0){
t[pos].val+=(t[pos].r-t[pos].l+1)*t[pos].mark;
if(t[pos].l!=t[pos].r){
t[pos*2+1].mark+=t[pos].mark;
t[pos*2].mark+=t[pos].mark;
}
t[pos].mark=0;
}
}
//下方标记
void update(int left,int right,int value,int pos)
{
check(pos);
if(left==t[pos].l&&right==t[pos].r){t[pos].mark=value;return;}
int m=(t[pos].l+t[pos].r)>>1;
if(left>m)update(left,right,value,2*pos+1);
else if(right<=m)update(left,right,value,2*pos);
else{update(left,m,value,2*pos);update(m+1,right,value,2*pos+1);}
check(2*pos+1);check(2*pos);
t[pos].val=t[2*pos].val+t[2*pos+1].val;
}
//区间更新
long long ask(int left,int right,int pos)
{
check(pos);
if(t[pos].l==left&&t[pos].r==right)return t[pos].val;
int m=(t[pos].l+t[pos].r)>>1;
if(left>m)return ask(left,right,2*pos+1);
else if(right<=m)return ask(left,right,2*pos);
return ask(left,m,2*pos)+ask(m+1,right,2*pos+1);
}
//区间询问
int _unique(int size){
int ct=0;
for(int i=0;i<size;ct++,i++){
g[ct]=g[i];l[ct]=l[i];r[ct]=r[i];
while(g[i]==g[i+1]){
l[ct]=min(l[ct],l[i+1]);
r[ct]=max(r[ct],r[i+1]);
i++;
}
}
g[ct]=0;
return ct;
}
//合并相同区间
int main()
{
int t;
scanf("%d",&t);
while(t--){
int n,cnt=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&num[i]);
built(1,n,1);
int qn;
scanf("%d",&qn);
for(int i=1;i<=qn;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+qn+1,cmp);
//记下询问并排序
memset(g,0,sizeof g);
for(int i=n,k=1;i>=1;i--){
//更新左端点信息,cnt为不同gcd的区间数
for(int j=0;j<cnt;j++){
int x=__gcd(num[i],g[j]);
g[j]=x;
}
g[cnt]=num[i];l[cnt]=r[cnt]=i;
++cnt;
cnt=_unique(cnt);
for(int j=0;j<cnt;j++)update(l[j],r[j],g[j],1);
//对左端点的连续区间在线段树里加上贡献
while(q[k].l==i){
ans[q[k].id]=ask(q[k].l,q[k].r,1);
k++;
}
//回答对于这个端点的询问
}
for(int i=1;i<=qn;i++)printf("%lld\n",ans[i]);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息