Luogu P5355 [Ynoi2017] 由乃的玉米田 题解

洛谷 P5355 [Ynoi2017] 由乃的玉米田

人生中第一道 Ynoi,多少得写点什么纪念一下。

题目链接

解析:

这道题算是P3674 小清新人渣的本愿的加强版,所以我们仍然可以使用类似的套路。

对于 \(1\)\(2\) 操作,利用 bitset 来维护序列中数的出现情况,\(3\) 操作直接枚举因数,不会的可以看我的这篇博客

主要的问题是 \(4\) 操作,我们先考虑暴力枚举,设 \(d = 1\),使得 \(d \times x ≤ \max\left \{ a_{i}\right \}\),再判断 \(d\)\(d \times x\)bitset 中是否出现过。
这样的复杂度是 \(O(\left \lfloor \frac{\max\left \{ a_{i} \right \}}{x} \right \rfloor)\) 的,可近似看做 \(O(\left \lfloor \frac{N}{x} \right \rfloor)\) 的,其中 \(N\) 为数据的上限。

不难发现,当 \(x\) 越大时,枚举的次数越少。考虑根号分治,设定一个阀值 \(S = \sqrt{N}\),当 \(x ≥ S\) 时,\(d ≤ \sqrt{N}\),可以保证复杂度最大是 \(O(\sqrt{N})\) 的;当 \(x < S\) 时,我们不用莫队,单独处理。

考虑如何单独处理,对于每一个 \(x\),从 \(1\)\(n\) 遍历一遍 \(a_{i}\),用 \(pre_{a_{i}}\) 表示 \(a_{i}\) 最近一次出现的位置。

之后进行一个类似扫描线的操作。设序列中存在两个数 \(a\)\(b\),满足 \(a,b\) 的商为 \(x\),即 \(b = a \div x\)\(b = a \times x\)。用 \(res_{i}\) 记录从位置 \(1\)\(i\) 能找到的 \(b\) 的最靠右(最大)的位置。

然后我们考虑如何求出这个 \(res\) 数组。既然说过了是一个类似扫描线的操作,那我们不妨设这根“线”为 \(l\),其记录着最近的 \(b\) 的位置 。\(res_{i}\) 要么来源于 \(a_{i}\),要么直接继承之前的 \(l\)。所以,当 \(pre_{a_{i} \times x}\)\(pre_{a_{i} \div x} > l\) 时,更新 \(l\),最后的 \(l\) 即为 \(res_{i}\) 的值。

设询问的左端点为 \(ql\),右端点为 \(qr\),若 \(ql ≤ res_{qr}\),表示询问的区间内满足有两数的商为 \(x\)

Code

#include<cmath>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<algorithm>

using namespace std;

const int MAXN = 1e5 + 10, N = MAXN, SQ = 300;
int n, m, siz, lim, tot;
int num[MAXN], ans[MAXN];
int pre[MAXN], res[MAXN];
int belong[MAXN], temp[MAXN];
bitset<MAXN> add, cut;

struct Question{
    int opt, l, r, x, id;
}q[MAXN];

vector<Question> p[MAXN];

bool cmp(const Question &a, const Question &b){
    if(belong[a.l] != belong[b.l]) return belong[a.l] < belong[b.l];
    if(belong[a.l] & 1) return a.r < b.r;
    return a.r > b.r;
}

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

void Add(int x){
    ++temp[x];
    if(temp[x] == 1){
        add[x] = 1;
        cut[N - x] = 1;
    }
}

void Del(int x){
    --temp[x];
    if(temp[x] == 0){
        add[x] = 0;
        cut[N - x] = 0;
    }
}

bool Check_Add(int x){
    return (add & (add << x)).any();
}

bool Check_Cut(int x){
    return (cut & (add << (N - x))).any();
}

bool Check_Mul(int x){
    for(register int d = 1; d * d <= x; d++){
        if(x % d != 0) continue;
        if(add[d] && add[x / d]) return true;
    }

    return false;
}

bool Check_Div(int x){
    for(register int d = 1; d * x <= MAXN; d++)
        if(add[d] && add[x * d]) return true;
    
    return false;
}

void Modify(int l, int r){
    for(register int i = 1; i <= tot; i++){
        int opt = q[i].opt, x = q[i].x;

        while(l < q[i].l) Del(num[l++]);
        while(l > q[i].l) Add(num[--l]);
        while(r < q[i].r) Add(num[++r]);
        while(r > q[i].r) Del(num[r--]);

        if(opt == 1) ans[q[i].id] = Check_Add(x);
        else if(opt == 2) ans[q[i].id] = Check_Cut(x);
        else if(opt == 3) ans[q[i].id] = Check_Mul(x);
        else if(opt == 4) ans[q[i].id] = Check_Div(x);
    }
}

void init(){
    for(register int x = 1; x <= SQ; x++){
        if(p[x].empty()) continue;

        int l = 0;
        memset(pre, 0, sizeof(pre));
        for(register int i = 1; i <= n; i++){
            pre[num[i]] = i;
            if(num[i] * x <= MAXN) l = max(l, pre[num[i] * x]);
            if(num[i] % x == 0) l = max(l, pre[num[i] / x]);
            res[i] = l;
        }

        for(register int i = 0; i < p[x].size(); i++){
            if(p[x][i].l <= res[p[x][i].r]) ans[p[x][i].id] = 1;
            else ans[p[x][i].id] = 0;
        }
    }
}

int main(){
    n = read(), m = read();
    for(register int i = 1; i <= n; i++){
        num[i] = read();
        lim = max(lim, num[i]);
    }

    siz = sqrt(n);
    for(register int i = 1; i <= n; i++)
        belong[i] = (i - 1) / siz + 1; 

    for(register int i = 1; i <= m; i++){
        int opt, l, r, x;
        opt = read(), l = read(), r = read(), x = read();
        if(opt == 4 && x <= SQ) p[x].push_back((Question){opt, l, r, x, i});
        else q[++tot] = (Question){opt, l, r, x, i};
    }

    init();

    sort(q + 1, q + 1 + tot, cmp);

    Modify(1, 0);

    for(register int i = 1; i <= m; i++){
        if(ans[i]) puts("yuno");
        else puts("yumi");
    }

    return 0;
}
posted @ 2022-09-20 20:00  TSTYFST  阅读(103)  评论(9编辑  收藏  举报