ZOJ-3800:Calculation(线段树+GCD)

 

题目链接:点击打开链接

 

题目大意:

给你 n 个数,会有多次查询 L,R,g 问你在 L,R这个区间有多少对 l 和 r  满足G(l,r)=g,

G( l,r )=g 即在下标从 l 到 r 这段区间所有数的最大公约数==g,

 

解题思路:

比赛的时候碰到的题目,本来以为是数论的,结果他喵越看越像线段树,后来发现这个查询操作还有点离线的意思啊。然后苦想两个多小时 gg ,真的想不出来思路,当时是想以每次查询的 g 为出发点离线的,但是感觉这样并没有什么实际的意义。赛后查了题解,果然 离线+线段树 。

 

首先说明思路来源:点击打开链接

其实当时比赛卡的地方主要就是如何维护一段区间内的任意两个数区间内的最大公约数,感觉怎么搞都是n^2的算法。

这里说下正确思路,语文不好,尽量讲的清楚一点。

其实这道题用到了一个不算性质的性质,就是对于一段区间来说,假设右端点已经确定了为 r,那么从它左边下标 1开始,G(1,r),G(2,r).....G(r-1,r),最后你维护出来的一定是一个非递减的序列,那么对于这个序列,我们可以进行合并,用一个pair或者结构体记录当前gcd的值和这个值已经在这段区间出现的次数,然后当我们改变右端点的时候即新增加一个数的时候,直接拿新增加的数跟前面出现的gcd值进行gcd即可,再更新每个数出现的次数,这就是一个维护过程。这里其实保证了前面的数的个数很少,具体怎么计算我也不清楚,跟约数有关吧,反正想想就知道肯定不多嘛^ - ^ 。

然后其实题目有一点说的很清楚 就是它让你查询的 g 值不会超过 50 个。就是因为这个条件当时才想着离线 g 来着。

这里呢其实我们就可以直接维护50棵线段树分别表示当前区间内对应的每个g值分别有多少个。这样说不太清楚,举个例子,假设当前你更新线段树中的(p,q)区间,其实就是对于(p,q)区间内的所有下标 k ,都有G(k,r)==g。为什么是一定是连续的一段区间呢,因为上面的性质,你维护出来的gcd序列一定是一个非递减的序列,那么相同的数当然就是连续的了。

这样最后总结一下思路,其实就是离线查询(l,r,g)的 r 。然后对题目给你的序列从第一个数开始往右扫,每次更新一下当前所有的gcd值和他们出现的次数,然后将这些更新到线段树中,然后得到当前以该数为右查询区间查询的所有查询的结果,然后扫下一个数,这样扫完一遍之后,所有查询的结果就得到了。

具体的代码实现其实都是看着上面那位大佬写的,主要感觉大佬的实现真的堪称完美了,所以代码几乎没啥改动,搞得我博客写原创都有点不好意思了= =。但是大佬的代码没咋写注释,我就多加点注释上去吧,还有一点要注意的就是这个代码的内存占用比较大,自己写实现的话,注意某些开 int 的地方不要开 long long ,不然可能会炸内存吧,反正写的时候注意内存的问题,还有交题的时候多交几次,我同一份代码同一种编译器分别交出了tle,4800+ms,1500+ms,总之就是玄学,以下附代码:

 

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<stack>
#include<set>
#include<cmath>
#include<vector>
#define pb push_back
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
typedef long long ll;
const int N = 100010;
struct node     //记录每次查询 分别存了左区间 g值 和当前查询的位置
{               //这里没有存查询的右区间 其实无所谓想存其实也可以存
    int L,qu,id;
    node(){}
    node(int LL,int qqu,int idx):L(LL),qu(qqu),id(idx) {}
};
vector<node> quv[N];    //查询的右区间其实就是这个数组的下标
pair<int,int> sv[N];    //记录某段区间内的所有gcd值及出现次数
int a[N],b[N],cv[N];    //这仨数组就不说了
int n,m,q,L,R,qu;
ll ans,res[N];      //res数组对应每个查询的答案
int gcd(int a,int b) {return b==0?a:gcd(b,a%b);}
struct tree     //结构体内部线段树,也是第一次尝试这种类似于类的写法,
{               //感觉维护多颗线段树还挺方便的样子
    ll sum[N<<2];   //线段树维护的值
    int lazy[N<<2]; //延迟标记
    void pushup(int rt)
    {
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    void pushdown(int rt,int l,int r)
    {
        if(lazy[rt])
        {
            lazy[rt<<1]+=lazy[rt];
            lazy[rt<<1|1]+=lazy[rt];
            int m=(l+r)>>1;
            sum[rt<<1]+=(ll)lazy[rt]*(m-l+1);   //注意理解这里为什么这样向下更新
            sum[rt<<1|1]+=(ll)lazy[rt]*(r-m);   //在slove函数里看这个区间是怎么来的
            lazy[rt]=0;
        }
    }
    void build(int l,int r,int rt)
    {
        int m=(l+r)>>1;
        sum[rt]=0;
        lazy[rt]=0;
        if(l==r)
            return ;
        build(l,m,rt<<1);
        build(m+1,r,rt<<1|1);
    }
    void update(int p,int q,int l,int r,int rt) //注意这里更新的值为(r-l+1)
    {
        if(l>=p&&r<=q)
        {
            sum[rt]+=(r-l+1);
            lazy[rt]++;
            return ;
        }
        pushdown(rt,l,r);
        int m=(l+r)>>1;
        if(p<=m)
            update(p,q,lson);
        if(q>m)
            update(p,q,rson);
        pushup(rt);
    }
    void query(int p,int q,int l,int r,int rt)  //正常的查询过程
    {
        if(l>=p&&r<=q)
        {
            ans+=sum[rt];
            return ;
        }
        pushdown(rt,l,r);
        int m=(l+r)>>1;
        if(p<=m)
            query(p,q,lson);
        if(q>m)
            query(p,q,rson);
        pushup(rt);
    }
}t[50];
void slove()
{
    int tot=0,k,cnt;
    node k1;
    for(int i=0;i<m;i++)    //初始化线段树
        t[i].build(1,n,1);
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<tot;j++)  //每次加入新数与前面所有的gcd进行gcd并更新
        {
            int tmp=gcd(sv[j].first,a[i]);
            sv[j].first=tmp;
        }
        if(tot>0&&sv[tot-1].first==a[i])    //这里呢其实是判断自己与自己gcd的情况
            sv[tot-1].second++;             //如果G(r-1,r)==a[r]的话那么直接次数++即可
        else                                //如果不相等就新建一个pair这个函数意思其实就是
            sv[tot++]=make_pair(a[i],1);    //sv[tot].first=a[i],sv[tot].second=1.
        k=0;
        for(int j=1;j<tot;j++)      //这里将相同数合并起来更新second
        {
            if(sv[j].first==sv[k].first)
                sv[k].second+=sv[j].second;
            else
                sv[++k]=sv[j];
        }
        tot=k+1;
        cnt=0;
        for(int j=0;j<tot;j++)  //注意理解这里比较关键
        {
            int pos=cv[sv[j].first];
            if(pos!=-1) //这里更新的l,r是由cnt推过来的,其实就是因为序列是非递减的
            {           //例如1,1,2,2,3,3,3,4,5.利用cnt就可表示出每个数分别对应的区间
                int l=cnt+1;//也就解释了pushdown函数的特殊更新过程
                int r=cnt+sv[j].second;
                t[cv[sv[j].first]].update(l,r,1,n,1);   //更新对应的线段树即可
            }
            cnt+=sv[j].second;
        }
        for(int j=0;j<(int)quv[i].size();j++)   //得到所有以当前i为查询右端点的查询的答案
        {
            k1=quv[i][j];
            ans=0;
            t[cv[k1.qu]].query(k1.L,i,1,n,1);
            res[k1.id]=ans;
        }
    }
}
int main()
{
    while(scanf("%d%d%d",&n,&m,&q)!=EOF)
    {
        memset(cv,-1,sizeof(cv));
        for(int i=0;i<=n;i++)   //初始化vector
            quv[i].clear();
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=0;i<m;i++)
        {
            scanf("%d",&b[i]);
            cv[b[i]]=i;         //记录每个b[i]对应的i
        }
        for(int i=0;i<q;i++)
        {
            scanf("%d%d%d",&L,&R,&qu);
            L++;
            quv[R].pb(node(L,qu,i));    //以右端点为下标记录每次查询
        }
        slove();
        for(int i=0;i<q;i++)
            printf("%lld\n",res[i]);    //按查询顺序输出答案
    }
    return 0;
}
/*
切记此题t的时候多交几发,真的玄学
*/

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值