HDU 5930 GCD

HDU 5930 GCD

线段树上二分

题意

设计数据结构支持:

  • 单点修改
  • 查询整个区间以及每个子区间有多少种不同的区间gcd

思路

线段树上的二分。

%%%

大体思路是这样:初始区间给定后,可以计算出每个gcd有多少个区间,用g数组记录一下。(因为最大的数字不超过1000000)
线段树维护区间gcd。每次修改,假设修改pos处的值,先计算所有受pos影响的区间的gcd与个数,在g数组上减去这些;在线段树单点修改pos的值,重复上述操作,在g数组上加回来。

问题就是怎么计算受pos影响的区间的gcd。两个query函数的变种:queryl(pos,gcdval,l,r,rt)queryr(pos,gcdval,l,r,rt)。负责查询pos处左侧(右侧)第一个与gcdval不同的gcd的值以及位置,亦即以pos为右(左)端点的直到数列尽头的,每个gcd不同的区间。这样每次查的结果存到数组,pos左侧存一个数组,pos右侧存一个数组,最后两个数组组合一下,更新g。

queryl为例,说一下:

//查询时
//R落在[l,mid],这时直接递归到左儿子
//R落在[mid+1,r],这时先查一下右儿子,如果gcd小于p,说明gcd变化了,那么返回查询结果
//如果gcd>=p,说明目前查到的[mid+1,pos]这些数都是pos这数的倍数,gcd没有变化,要再查左儿子
//递归基有两种情况,某个完全在左侧的区间的公约数%p==0,说明这些数全比pos处大,直接返回p就行,或者查到了叶子,也要返回
//感觉好复杂,自己理解吧
pair<int, int> queryl(int R, int p, int l, int r, int rt)
{
    if(r<=R)
    {
        if(stree[rt]%p==0) return make_pair(p, l);
        else if(l==r) return make_pair(gcd(p, stree[rt]), l);//l实际改成1也能过,是一样的
    }
    int mid=(l+r)>>1;
    if(R<=mid) return queryl(R, p, lson);
    pair<int, int> tmp=queryl(R, p, rson);
    if(tmp.first<p) return tmp;
    return queryl(R, p, lson);
}

对了,初始统计g数组时也用了上面的方法。

代码

不知道以后遇到这种题能不能写得出来

#include <bits/stdc++.h>
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define M(a,b) memset(a,b,sizeof(a))
using namespace std;
const int MAXN=50005, MAXG=1000007;
int stree[MAXN<<2], num[MAXN];
int g[MAXG];

inline int gcd(int a, int b) { return b==0 ? a : gcd(b, a%b); }
inline void pushup(int rt) { stree[rt]=gcd(stree[rt<<1], stree[rt<<1|1]); }
void build(int l, int r, int rt)
{
    if(l==r) { stree[rt]=num[l];return; }
    int mid=(l+r)>>1;
    build(lson);
    build(rson);
    pushup(rt);
}
void update(int pos, int val, int l, int r, int rt)
{
    if(l==r)
    {
        stree[rt]=val;
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid) update(pos, val, lson);
    else update(pos, val, rson);
    pushup(rt);
}
int query(int L, int R, int l, int r, int rt)
{
    if(L<=l&&r<=R) return stree[rt];
    int mid=(l+r)>>1;
    if(mid>=R) return query(L, R, lson);
    else if(L>mid) return query(L, R, rson);
    else return gcd(query(L, R, lson), query(L, R, rson));
}
pair<int, int> queryl(int R, int p, int l, int r, int rt)
{
    if(r<=R)
    {
        if(stree[rt]%p==0) return make_pair(p, l);
        else if(l==r) return make_pair(gcd(p, stree[rt]), l);
    }
    int mid=(l+r)>>1;
    if(R<=mid) return queryl(R, p, lson);
    pair<int, int> tmp=queryl(R, p, rson);
    if(tmp.first<p) return tmp;
    return queryl(R, p, lson);
}
pair<int, int> queryr(int L, int p, int l, int r, int rt)
{
    if(L<=l)
    {
        if(stree[rt]%p==0) return make_pair(p, l);
        else if(l==r) return make_pair(gcd(p, stree[rt]), l);
    }
    int mid=(l+r)>>1;
    if(L>mid) return queryr(L, p, rson);
    pair<int, int> tmp=queryr(L, p, lson);
    if(tmp.first<p) return tmp;
    return queryr(L, p, rson);
}
void calc(int p, int f, int &s, int n)
{
    vector<pair<int, int>> pl, pr;
    int nowgcd, finalgcd, curpos;

    nowgcd=num[p], finalgcd=query(1, p, 1, n, 1), curpos=p;
    pl.push_back(make_pair(nowgcd, p));
    while(nowgcd!=finalgcd)
    {
        pair<int, int> tmp=queryl(curpos, nowgcd, 1, n, 1);
        pl.push_back(tmp);
        nowgcd=tmp.first, curpos=tmp.second;
    }
    pl.push_back(make_pair(nowgcd, 0));

    nowgcd=num[p], finalgcd=query(p, n, 1, n, 1), curpos=p;
    pr.push_back(make_pair(nowgcd, p));
    while(nowgcd!=finalgcd)
    {
        pair<int, int> tmp=queryr(curpos, nowgcd, 1, n, 1);
        pr.push_back(tmp);
        nowgcd=tmp.first, curpos=tmp.second;
    }
    pr.push_back(make_pair(nowgcd, n+1));

    for(int i=1;i<pl.size();i++)
    {
        int lenl=pl[i-1].second-pl[i].second;
        int gcdl=pl[i-1].first;
        for(int j=1;j<pr.size();j++)
        {
            int lenr=pr[j].second-pr[j-1].second;
            int gcdr=pr[j-1].first;
            int num=f*lenl*lenr;

            int tmpgcd=gcd(gcdl, gcdr);
            if(g[tmpgcd]==0) s++;
            g[tmpgcd]+=num;
            if(g[tmpgcd]==0) s--;
        }
    }
}
int main()
{
    int T;
    scanf("%d", &T);int cas=0;
    while(T--)
    {
        printf("Case #%d:\n", ++cas);
        int gcdsum=0;
        M(g, 0);
        int n, op;scanf("%d%d", &n, &op);
        for(int i=1;i<=n;i++) scanf("%d", &num[i]);
        build(1, n, 1);
        for(int i=1;i<=n;i++)
        {
            int nowgcd=num[i], finalgcd=query(1, i, 1, n, 1), curpos=i;
            while(nowgcd!=finalgcd)
            {
                pair<int, int> tmp=queryl(curpos, nowgcd, 1, n, 1);
                if(!g[nowgcd]) gcdsum++;
                g[nowgcd]+=curpos-tmp.second;
                nowgcd=tmp.first, curpos=tmp.second;
            }
            if(!g[finalgcd]) gcdsum++;
            g[finalgcd]+=curpos;
        }
        while(op--)
        {
            int p, v;scanf("%d%d", &p, &v);
            calc(p, -1, gcdsum, n);
            num[p]=v;
            update(p, v, 1, n, 1);
            calc(p, 1, gcdsum, n);
            printf("%d\n", gcdsum);
        }
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值