[HEOI2015]公约数数列

一、题目

点此看题

二、解法

对于这种维护奇怪东西的题就可以考虑分块 q w q qwq qwq

看上去 gcd ⁡ \gcd gcd和异或没有什么联系,也不好一起维护,所以我们分开维护,本题的复杂度是由于一个数组中前缀 gcd ⁡ \gcd gcd的取值最多 log ⁡ \log log种,因为改变一次至少要减 1 2 \frac{1}{2} 21

那么每一个块我们维护这些值:块内 gcd ⁡ \gcd gcd;前缀 x o r xor xor (整个数组);单个元素值; x o r xor xor修改标记。为了方便查询,我们先把所有的前缀 x o r xor xor塞进 s e t set set中,修改就十分暴力了,先暴力重构当前块内的 gcd ⁡ \gcd gcd x o r xor xor,然后后面的块直接打 x o r xor xor修改标记就行了(相当于懒标记)。

查询的话维护一个前缀 gcd ⁡ \gcd gcd,如果经过这个块 gcd ⁡ \gcd gcd不会改变的话,就说明块内 gcd ⁡ \gcd gcd前缀和是一样的,直接在 s e t set set中查询即可。否则说明块内改变了前缀 gcd ⁡ \gcd gcd,直接暴力访问块内每一个位置(不会超过 log ⁡ \log log次)。

总时间复杂度 O ( n n log ⁡ n ) O(n\sqrt n\log n) O(nn logn),这道题不怎么卡常,贴个代码 q w q qwq qwq,顺便说一句,注意异或和等号优先级的问题,异或的优先级不比等号高(所以必须打括号)

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <set>
using namespace std;
const int M = 100005;
#define ll long long
#define mp make_pair
#define int long long
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,m,k,nn,a[M],xp[M],g,ans;ll p;
char str[10];
int bel(int n)
{
    return (n-1)/nn+1;
}
int gcd(int a,int b)
{
    return !b?a:gcd(b,a%b);
}
struct block
{
    int val[320],L,R,len,Gcd,x[320],tag;
    set<pair<int,int> >s;
    void reset()
    {
        Gcd=val[1];
        for(int i=2;i<=len;i++) Gcd=gcd(Gcd,val[i]);
    }
    void build(int l,int r)
    {
        L=l;R=r;len=r-l+1;
        for(int i=l;i<=r;i++)
        {
            val[i-l+1]=a[i];
            x[i-l+1]=xp[i];
            s.insert(mp(xp[i],i-l+1));
        }
        reset();
    }
    void modify(int pos,int dlt)
    {
        pos=pos-L+1;
        for(int i=pos;i<=len;i++)
        {
            s.erase(mp(x[i],i));
            x[i]^=dlt;
            s.insert(mp(x[i],i));
        }
        val[pos]^=dlt;
        reset();
    }
    void get()
    {
        for(int i=1;i<=len;i++)
        {
            g=gcd(g,val[i]);
            if((ll)g*(x[i]^tag)==p)
            {
                ans=i+L-1;
                return ;
            }
        }
    }
    void Find()
    {
        if(p%g) return ;
        set<pair<int,int> >::iterator it;
        it=s.lower_bound(mp((p/g)^tag,0));
        if(it==s.end() || (it->first^tag)!=(p/g)) return ;
        //异或的优先级竟然没有等号高[\doge]!!
        ans=it->second+L-1;
    }
}b[320];
signed main()
{
    n=read();nn=sqrt(n);
    for(int i=1;i<=n;i++)
        a[i]=read(),xp[i]=xp[i-1]^a[i];
    m=read();k=bel(n);
    for(int i=1;i<k;i++) b[i].build((i-1)*nn+1,i*nn);
    b[k].build((k-1)*nn+1,n);
    while(m--)
    {
        scanf("%s",str);
        if(str[0]=='M')
        {
            int pos=read()+1,x=read();
            int dlt=a[pos]^x;a[pos]=x;
            for(int i=bel(pos)+1;i<=k;i++) b[i].tag^=dlt;
            b[bel(pos)].modify(pos,dlt);
        }
        else
        {
            scanf("%lld",&p);
            g=0;ans=-1;
            for(int i=1;i<=k && ans==-1;i++)
            {
                if(g==gcd(g,b[i].Gcd))
                    b[i].Find();
                else
                    b[i].get();
            }
            if(ans==-1) puts("no");
            else printf("%lld\n",ans-1);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值