题解 P4108【[HEOI2015]公约数数列】

2023.6.21:题解已过审,但是发现有一处复杂度计算错误,修改后重新提交。

看到这种奇怪的操作,首先想到分块。

以下记值域为 w,块长为 B

前缀 gcd 显然单调不增,而且后一个必须是前一个的因数,如果变化至少要减半。因此,我们知道,共有 O(logw) 个不同的前缀 gcd。我们可以接受对这些块暴力,只需要对前缀 gcd 都相同的块能快速求答案即可。

有以上思路做铺垫,容易想到设 l(x) 为第 x 块的左端点(含),r(x) 为第 x 块的右端点(含),pos(i) 为第 i 个数所在块,bgcd(x) 为第 x 块的 gcdbxor(i) 表示区间 [l(pos(i)),i] 的异或和。同时,我们对每个块维护一个 map bfirst,表示这个块内每个值作为 bxor 第一次出现的位置。

对于查询 x 操作,从前往后扫描每个块,维护当前 gcd 和异或和。利用 bgcd 容易判断这个块内前缀 gcd 是否发生变化。如果发生变化,我们暴力整个块检查;如果未发生变化,即求 bfirst(xgs),其中 g,s 为此前所有块的 gcd 和异或和。暴力检查的块数为 O(logw),这部分复杂度 O(Blogw);其他块的复杂度为 O(nBlogB)

对于单点修改操作,暴力改所在块即可。复杂度 O(BlogB)

B=n,总复杂度 O(nlogn+qnlogw)

这就是开头处提到的复杂度计算错误。原本我认为调用了 O(qnlogw)gcd 函数(见代码),复杂度应为 O(qnlog2w)。但是事实上只有 O(qlogw) 次调用不为 gcd(x,x) 的形式,因为前缀 gcd 只变化 O(logw) 次。而我们注意到,gcd(x,x) 的复杂度为 O(1)。事实上,考虑辗转相除法的过程,gcd(x,y) 的复杂度为 O(logmax{x,y}gcd(x,y))。因此,总复杂度为 O(qnlogw)

//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(ll x=(y);x<=(z);x++)
#define per(x,y,z) for(ll x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;

mt19937 rnd(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
ll randint(ll L, ll R) {
    uniform_int_distribution<ll> dist(L, R);
    return dist(rnd);
}

template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}

const ll N = 1e5+5, BS = 505;

ll n, a[N], q, L[BS], R[BS], pos[N], B, tot, bgcd[BS], bxor[N];
map<ll, ll> bfirst[BS];

void init() {
    B = sqrt(n);
    chkmin(B, n);
    chkmax(B, 1LL);
    while(++tot) {
        L[tot] = R[tot-1] + 1;
        R[tot] = min(tot*B, n);
        rep(i, L[tot], R[tot]) {
            pos[i] = tot;
            bgcd[tot] = __gcd(bgcd[tot], a[i]);
            bxor[i] = (i == L[tot] ? 0 : bxor[i-1]) ^ a[i];
            if(!bfirst[tot].count(bxor[i])) bfirst[tot][bxor[i]] = i;
        }
        if(R[tot] == n) break;
    }
}

void modify(ll p, ll k) {
    a[p] = k;
    bgcd[pos[p]] = 0;
    map<ll, ll>().swap(bfirst[pos[p]]);
    rep(i, L[pos[p]], R[pos[p]]) {
        bgcd[pos[p]] = __gcd(bgcd[pos[p]], a[i]);
        bxor[i] = (i == L[pos[p]] ? 0 : bxor[i-1]) ^ a[i];
        if(!bfirst[pos[p]].count(bxor[i])) bfirst[pos[p]][bxor[i]] = i;
    }
}

ll query(ll x) {
    ll gcd = 0, now = 0;
    rep(i, 1, tot) {
        if(gcd != __gcd(gcd, bgcd[i])) {
            rep(j, L[i], R[i]) {
                gcd = __gcd(gcd, a[j]);
                now ^= a[j];
                if(gcd * now == x) return j;
            }
        }
        else {
            if(x % gcd == 0 && bfirst[i].count((x/gcd)^now)) return bfirst[i][(x/gcd)^now];
            now ^= bxor[R[i]];
        }
    }
    return -1;
}

int main() {
    scanf("%lld", &n);
    rep(i, 1, n) scanf("%lld", &a[i]);
    init();
    for(scanf("%lld", &q); q; q--) {
        char op[8];
        scanf("%s", op);
        if(op[0] == 'M') {
            ll p, k;
            scanf("%lld%lld", &p, &k);
            modify(p+1, k);
        }
        else {
            ll x;
            scanf("%lld", &x);
            ll ans = query(x);
            if(ans == -1) puts("no");
            else printf("%lld\n", ans-1);
        }
    }
    return 0;
}
posted @   rui_er  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示