http://blog.youkuaiyun.com/saber_acher/article/details/52497104
先对所有i求出它在【1,i】所有子区间的gcd。
这个其实很好求的把a【i】和【1,i-1】所有的gcd比较一下,按递减的顺序push_back,保证不重不漏。
递减是个非常聪明的优化,这样可以避免set。
事先输入所有询问,然后一边遍历所有位子当区间右端点,一边维护线段树,一边回答问题。
突然觉得这个套路好熟悉,最近做的题目多有这种感觉,只不过,有的题目是直接要你求出所有结果,而不是一个个询问,有的题目维护的是单调栈或线段树,而不是树状数组。按顺序枚举,如果枚举中经过了要问的问题,那就直接回答。因此我们只需要维护线段树,使得能回答当前问题,而且今后可以回答后面的问题就可以了,至于已经回答过的问题,就扔掉了,也就不必去保证能回答过去的问题。
在此题中,遍历所有位子当区间右端点。每次先更新新加入的gcd。删除重复的gcd。更新完后,回答所有可以回答的问题。并保存结果。
最后输出。
如果把问题按右端点升序排序,那么寻找可以回答的问题就会十分简单。但是你要保存这个问题是第几个问的,输出的时候就要按顺序输出。
代码
#include<bits/stdc++.h>
#define maxn 100010
using namespace std;
typedef pair<int,int> pii;
int N,Q;
int a[maxn];
vector<pii>vec[maxn];
int tree[maxn];
int vis[10*maxn];
int ANS[maxn];
struct qry
{
int l,r,id;
qry(int a,int b,int c):l(a),r(b),id(c){}
bool operator < (const qry& rhs) const
{
return r<rhs.r;
}
};
vector<qry>QRY;
void add(int pos,int val)
{
while(pos<=N)
{
tree[pos]+=val;
pos+=pos&(-pos);
}
}
int sum(int pos)
{
int ret=0;
while(pos)
{
ret+=tree[pos];
pos-=pos&(-pos);
}
return ret;
}
int main()
{
while(scanf("%d %d",&N,&Q)!=EOF)
{
QRY.clear();
memset(tree,0,sizeof(tree));
for(int i=1;i<=N;i++)
{
scanf("%d",&a[i]);
vec[i].clear();
}
for(int i=1;i<=N;i++)
{
int x=a[i];
int y=i;
for(unsigned int j=0;j<vec[i-1].size();j++)
{
int GCD=__gcd(x,vec[i-1][j].first);
if(x!=GCD)
{
vec[i].push_back(make_pair(x,y));
x=GCD;y=vec[i-1][j].second;
}
}
vec[i].push_back(make_pair(x,y));
}
int l,r;
for(int i=1;i<=Q;i++)
{
scanf("%d %d",&l,&r);
QRY.push_back(qry(l,r,i));
}
sort(QRY.begin(),QRY.end());
memset(vis,0,sizeof(vis));
int k=0;
for(int i=1;i<=N;i++)
{
for(unsigned int j=0;j<vec[i].size();j++)
{
int x=vec[i][j].first;
int y=vec[i][j].second;
if(vis[x]) add(vis[x],-1);
vis[x]=y;
add(y,1);
}
while(QRY[k].r==i)
{
ANS[QRY[k].id]=sum(QRY[k].r)-sum(QRY[k].l-1);
k++;
}
}
for(int i=1;i<=Q;i++)
printf("%d\n",ANS[i]);
}
return 0;
}