这两道题手段挺类似的。
hdu4630:
让求区间中任意两个数最大的公约数,这种题目常用手段便是离线化,集中用动态规划处理,对这个题目可以这么记录。对于查询,按照r从小到大排序,然后维护一个线段树,线段树的叶子节点记录的是该叶子节点表示的数与当前更新的位置之间两个数的公约数中最大的一个,每次记录的是该约数上次出现的位置,这样对出现过的每个约数,能形成一条由离散的叶子节点组成的链,对于一个叶子节点一旦出现某个约数,证明当前处理的尾节点和该叶子节点出现了同一个约数。这样便可以通过移动线段树的尾节点,维护线段树前面部分,处理到当前位置,则对以当前位置为结尾的查询进行处理,然后记录查询区间内的最大值即可。
附代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define delf int m=(l+r)>>1
int n,m;
const int MAX=50000+1;
int tree[MAX<<2];
int a[MAX];
int ans[MAX];
int v[MAX];
struct qq
{
int l;
int r;
int id;
} q[MAX];
bool cmp(qq a,qq b)
{
return a.r<b.r;
}
void build(int l,int r,int rt)
{
tree[rt]=1;
if (l==r)
return ;
delf;
build(lson);
build(rson);
return ;
}
int max(int a,int b)
{
return a>b?a:b;
}
void pushup(int rt)
{
tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
void update(int k,int v,int l,int r,int rt)
{
if (l==r)
{
tree[rt]=max(v,tree[rt]);
return ;
}
delf;
if (m>=k)
update(k,v,lson);
else
update(k,v,rson);
pushup(rt);
return ;
}
int query(int L,int R,int l,int r,int rt)
{
if (L<=l&&r<=R)
return tree[rt];
delf;
if (m>=R)
return query(L,R,lson);
if (m<L)
return query(L,R,rson);
return max(query(L,R,lson),query(L,R,rson));
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
build(1,n,1);
memset(a,0,sizeof(a));
for (int i=1;i<=n;i++)
scanf("%d",&v[i]);
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int cur=1;
for (int i=1;i<=n;i++)
{
for (int t=1;t*t<=v[i];t++)
{
if (v[i]%t==0)
{
if (a[t]!=0)
update(a[t],t,1,n,1);
int t1=v[i]/t;
if (t!=t1&&a[t1]!=0)
update(a[t1],t1,1,n,1);
a[t1]=i;
a[t]=i;
}
}
while (cur<=m&&q[cur].r<=i)
{
if (q[cur].l==q[cur].r)
ans[q[cur].id]=0;
else
ans[q[cur].id]=query(q[cur].l,q[cur].r,1,n,1);
cur++;
}
}
for (int i=1;i<=m;i++)
printf("%d\n",ans[i]);
}
}
hdu4638
多校的又一道线段树题目。
这个题目让给定区间内能分出的连续数串的个数。这个题目乍一看挺水,可是越看越不好写,参考了下大神做法,茅塞顿开。
题目让求连续的串的个数,那么我们可以先假设任意一个数都形成一个独立的串,然后看有哪些串能合并,一旦合并串的总数减1,直至不能合并,便是结果。
这个题依旧要离线化,也是按照查询的右边界升序排序,将数字一个个添加进入线段树的时候维护线段树,同时在刚好维护到查询区间的右部的时候进行查询并输出结果。
可以这么记录,对于每个数,默认其为一个独立的串,将这个位置的串的个数更新为1,然后再看比它小的那个数在之前的区间出现过没有。如果出现过,则从上次出现的位置或者上次出现位置之前的部分的任意位置与当前位置形成的区间都能将这两个数合并为1个串,于是在上次出现的位置的串数进行-1操作即可,对比它大的那个数进行同样处理,注意不能是赋值为0,如果赋值为0,例如之前出现的是3,当前位置是2,那么便将3的位置赋值为0,2的位置赋值为1,然后再来一个4的话,3的位置仍然赋值为0,4的位置赋值为1,这样查询这三个数形成的区间便输出2,而答案是1,原因是,叶子节点记录的是相对串的值而不是绝对串的值,这样做能保证结果正确的原因是每次查询处理的时候,都是恰好维护到查询的右端点位置。保证了右端点位置肯定被查询包含进去,而右端点之后的位置还未更新,右端点之前的位置的结果都是相对右端点的结果,因此可以这样记录。
附代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define delf int m=(l+r)>>1
const int MAX=100000+5;
int n,m;
int sum[MAX<<2];
int v[MAX];
int mark[MAX];
int ans[MAX];
void build(int l,int r,int rt)
{
sum[rt]=0;
if (l==r)
return ;
delf;
build(lson);
build(rson);
return ;
}
void pushup(int rt)
{
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void update(int k,int v,int l,int r,int rt)
{
if (l==r)
{
sum[rt]+=v;
return ;
}
delf;
if (k<=m)
update(k,v,lson);
else
update(k,v,rson);
pushup(rt);
return ;
}
int query(int L,int R,int l,int r,int rt)
{
if (L<=l&&r<=R)
return sum[rt];
delf;
if (m>=R)
return query(L,R,lson);
if (m<L)
return query(L,R,rson);
return query(L,R,lson)+query(L,R,rson);
}
struct node
{
int l;
int r;
int id;
} q[MAX];
bool cmp(node a,node b)
{
return a.r<b.r;
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&m);
build(1,n,1);
for (int i=1;i<=n;i++)
scanf("%d",&v[i]);
for (int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
memset(mark,0,sizeof(mark));
int cur=1;
for (int i=1;i<=n;i++)
{
update(i,1,1,n,1);
if (mark[v[i]+1]!=0)
update(mark[v[i]+1],-1,1,n,1);
if (mark[v[i]-1]!=0)
update(mark[v[i]-1],-1,1,n,1);
mark[v[i]]=i;
while (cur<=m&&q[cur].r<=i)
{
ans[q[cur].id]=query(q[cur].l,q[cur].r,1,n,1);
cur++;
}
}
for (int i=1;i<=m;i++)
printf("%d\n",ans[i]);
}
}