[BZOJ5358][Lydsy1805月赛]口算训练(质因数分解+离线乱搞)

乱搞?????又是?????

Address

https://begin.lydsy.com/JudgeOnline/upload/201805.pdf

Solution

发现好多人用主席树
这里讲一种跑得更快的离线做法。
首先,对于每个询问 (l,r,d) ( l , r , d ) ,可以将 d d 分解质因数。
如果分解的结果是 d=i=1rpiqi ,并且质因子 x x [l,r] 内所有数中出现的次数之和为 sum[l,r,x] s u m [ l , r , x ] ,那么询问转化为,判断是否满足:

1ir,sum[l,r,pi]qi ∀ 1 ≤ i ≤ r , s u m [ l , r , p i ] ≥ q i

尝试把每个数进行质因数分解(由于有多组数据,因此可以预处理出 [1,105] [ 1 , 10 5 ] 内所有数的质因数分解以提高效率,而一个 [1,105] [ 1 , 10 5 ] 的质因子个数不超过 6 6
把询问 sum[l,r,x] 拆成 sum[1,l1,x] s u m [ 1 , l − 1 , x ] sum[1,r,x] s u m [ 1 , r , x ]
于是询问再次转化:求一个前缀内,质因子 x x 的出现次数。
但可以发现,如果对于每个质因子都维护一个前缀和,那么空间是 O(n2) 的,开不下。
于是将询问离线,按照前缀的长度(末尾标号)从小到大排序。
用一个指针 i i 将序列从左到右扫描一遍,扫描的过程中,维护前缀 [1,i] 内各个因子的出现次数。这样,当 i=k i = k 的时候,就能立即回答形如 sum[k,x] s u m [ k , x ] 的询问。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, M = N << 1, E = 10;
int n, m, w, a[N], tot, pri[N], pr[N], num[N][E], pw[N][E], qn[N], cnt[N],
ans[N][E]; bool mark[N]; struct cyx {int i, id;} q[M];
inline bool comp(const cyx &a, const cyx &b) {return a.i < b.i;}
void sieve() {
    int i, j; mark[0] = mark[1] = 1; For (i, 2, 100000) {
        if (!mark[i]) pri[++tot] = i; For (j, 1, tot) {
            if (1ll * i * pri[j] > 100000) break; mark[i * pri[j]] = 1;
            if (i % pri[j] == 0) break;
        }
    }
    For (i, 1, 100000) {
        int x = i; For (j, 1, tot) {
            int y = pri[j]; if (1ll * y * y > i) break;
            if (x % y) continue; num[i][++pr[i]] = y;
            while (x % y == 0) pw[i][pr[i]]++, x /= y;
        }
        if (x > 1) num[i][++pr[i]] = x, pw[i][pr[i]] = 1;
    }
}
void work() {
    int i, j, l, r; n = read(); m = read(); For (i, 1, n) a[i] = read(); w = 0;
    For (i, 1, m) {
        For (j, 1, 6) ans[i][j] = 0; l = read(); r = read(); qn[i] = read();
        q[++w] = (cyx) {l - 1, w - 1}; q[++w] = (cyx) {r, w - 1};
    }
    sort(q + 1, q + w + 1, comp); memset(cnt, 0, sizeof(cnt)); l = 1;
    For (i, 0, n) {
        if (i) For (j, 1, pr[a[i]]) cnt[num[a[i]][j]] += pw[a[i]][j];
        while (l <= w && q[l].i == i) {
            int id = (q[l].id >> 1) + 1; For (j, 1, pr[qn[id]])
                if (q[l].id & 1) ans[id][j] += cnt[num[qn[id]][j]];
                else ans[id][j] -= cnt[num[qn[id]][j]]; l++;
        }
    }
    For (i, 1, m) {
        double res = 1; For (j, 1, pr[qn[i]])
            res = res && ans[i][j] >= pw[qn[i]][j]; puts(res ? "Yes" : "No");
    }
}
int main() {
    int T = read(); sieve(); while (T--) work(); return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值