Nowcoder-小乐乐学数学(树状数组+离线询问)

今天学长刚刚总结的一类题型

给出了两道例题,一道是CF220B

另一个是牛客的这道

题意(CF220B):

每次询问区间[L,R]有多少个数x出现了刚好x次

题意(牛客):

每次询问[L,R]有多少数字,a[i]满足和区间内的其他数都互质

Trick:

自己总结了一下这样的题的一个算是一个通用的trick吧,首先是对于多次询问的题目 ,

然后因为我们需要把询问离线到端点上(具体操作看代码就懂),所以每次询问所涉及到的数字必须是在区间[L,R]内的,就是说区间外的数并不对区间内产生直接的影响,

然后一般是询问区间的内的点对这类问题,因为找  点对  比较好的入手的方式就是看每个点的贡献,但是每次询问又不能扫一遍区间,所以我们只扫一遍区间,在端点上记录询问信息

思路(牛客):

Q1:考虑一个数需要满足什么样的条件和区间所有的数都互质

A1: 这个质因子分解后和所有的数都没有共同的质因子

那么首先我们需要在logn的时间复杂度内求出一个数的所有质因子(因为需要对每一个数都要质因子分解)

Q2:对于当前的数x,质因子分解后得到p1,p2,p3怎么计算贡献

A2:假设我们知道p1,p2,p3上一次出现的位置即[1,i-1],找一个最大值ma,那么区间[ma+1,i]这一段的贡献一定是1

Q3:怎么消除一个位置的贡献

A3:消除和添加这里都选择用树状数组实现,维护差分序列,维护单点修改,区间查询

操作细节

假设当前操作到的数字是a[i],质因子分解后是p1,p2,p3

首先找到Q2的ma,然后单点修改两个点的值

然后

找到p1,p2,p3上一次出现的位置 (如果是第一次消除这个位置的贡献,那么这个位置的贡献一定是1) 单点修改值

p1,p2,p3被修改为1的时候他们都各自有一个ma1,ma2,ma3被修改为-1,消除贡献的时候要把ma1,ma2,ma3改回0

具体操作看代码

 CODE:

#include "bits/stdc++.h"
using namespace std;
typedef long long ll;

const int maxn = 2e5 + 7;

#define mst(x, a) memset(x, a, sizeof(x))
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)

int n, m, a[maxn], ans[maxn];
vector<pair<int, int>> e[maxn];
int pos[maxn], pre[maxn], flag[maxn];

int t[maxn];
int lowbit(int x) {
    return x & (-x);
}
void add(int x, int op) {
    if (x == 0)
        return;
    while (x <= n) {
        t[x] += op;
        x += lowbit(x);
    }
}
int query(int x) {
    int res = 0;
    while (x > 0) {
        res += t[x];
        x -= lowbit(x);
    }
    return res;
}
int xx, p[maxn], mi[maxn], vis[maxn];
void oula() {
    for (int i = 2; i < maxn - 2; i++) {
        if (vis[i] == 0)
            p[++xx] = i, mi[i] = i;
        for (int j = 1; j <= xx && i * p[j] < maxn - 2; j++) {
            vis[i * p[j]] = 1;
            mi[i * p[j]] = p[j];
            if (i % p[j] == 0)
                break;
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    oula();/*预处理出每个数的最小质因子*/
    while (cin >> n >> m) {
        rep(i, 1, n) cin >> a[i], t[i] = pos[i] = flag[i] = pre[i] = 0;
        rep(i, 1, n) e[i].clear();
        rep(i, 1, m) {
            int x, y;
            cin >> x >> y;
            /*将询问离线到端点*/
            e[y].push_back({x, i});
        }

        for (int i = 1; i <= n; i++) {
            /*logn的质因子分解*/
            int t = a[i];
            set<int> s;
            while (t > 1) {
                int mm = mi[t];
                s.insert(mm);
                t /= mm;
            }

            /*找出距离i最近的一个质因子(其最后一次出现的位置)*/
            int ma = 0;
            for (auto num : s)
                ma = max(ma, pos[num]);
                
            /* 消除 相同质因子的贡献 */
            for (auto num : s) {
                if (flag[pos[num]] == 0) {
                    add(pos[num], -1);
                    if (pre[pos[num]])
                        add(pre[pos[num]], 1);
                    flag[pos[num]] = 1;
                }
            }
            /*更新每个质因子最后一次出现的位置*/
            for (auto num : s)
                pos[num] = i;

            
            add(i, 1);
            pre[i] = ma;
            add(ma, -1);
            for (auto fr : e[i])
                ans[fr.second] = query(i) - query(fr.first - 1);
        }
        rep(i, 1, m) cout << ans[i] << endl;
    }

    return 0;
}
/*

*/
View Code
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值