1010考试T3

1010考试T3

​ 题目大意:

​ 有一个序列有两个操作:

\(1 \ [l, r]\) 表示在区间\([l, r]\)中选出两个非空集合,使这两个集合没有交集。如果可以找到输出\(Yes\),否则输出\(No\)

\(2 \ [l, r]\)表示将区间内所有数\(a[i]\)变为\(a[i] ^ 3 \% v\)

\(n <= 100000, v <= 1000\)

​ 线段树。

​ 看完题解觉得这题也还行,但考场上就是不会做。

​ 先看操作一怎么搞:

​ 我们可以知道集合的个数为:\(2 ^ {len}\)\(len\)表示区间里有几个数,还可以知道区间所有数的总和为\([0, len * v]\)。根据抽屉原理,可以知道:如果\(2 ^ {len} > len *v + 1\),那么肯定会有两个集合的总和相同,所以只要这个判断条件成立,我们就可以直接输出\(Yes\)。如果说这两个集合有交集怎么办?其实只要把这两个集合的交集都去掉,剩下的数的总和还是相等的,抽屉原理依然正确。

​ 那如果不成立呢?因为\(v <= 1000\)我们可以解出\(len >= 14\)是成立,那么我们最多就会暴力找13个数,直接\(dfs\),先搜前一半,搜后一半,最坏复杂度是\(3 ^ 6 + 3 ^ 7\),加上一些剪枝根本达不到这么多。

​ 再看操作二怎么搞:

​ 让线段树维护一个二操作的次数,每次下放到叶子节点在修改,因为最多改13个数,复杂度就可以过。可是每次暴力乘三次方太慢了,我们发现\(v\)的范围很小,所以可以预处理出\([0, 1000)\)所有数的三次方是多少,用倍增预处理就好了。\(f[i][0] = i * i *i \% v\) \(f[i][j] = f[f[i][j - 1]][j - 1]\)

#include <bits/stdc++.h>

#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
#define mid ((l + r) >> 1)

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 1e5 + 5;
int n, m, v, opt, l, r, p, cnt;
int a[N], sta[N << 2], f[1005][21], tag[N << 2];
bool vis[N];

void make_pre() {
    for(int i = 0;i < v; i++) f[i][0] = i * i % v * i % v;
    for(int j = 1;j <= 20; j++) 
        for(int i = 0;i < v; i++) f[i][j] = f[f[i][j - 1]][j - 1]; //倍增预处理
}

void down(int o) {
    if(tag[o]) tag[ls(o)] += tag[o], tag[rs(o)] += tag[o], tag[o] = 0;
}

void up(int &a, int res) {
    int i = 20;
    while(i >= 0) {
        if(res >= (1 << i)) {
            res -= (1 << i);
            a = f[a][i];
        }
        if(res == 0) return ;
        i --;
    }
}

void change(int o, int l, int r, int x, int y) {
    if(x <= l && y >= r) { tag[o] ++; return ; }
    down(o); 
    if(x <= mid) change(ls(o), l, mid, x, y);
    if(y > mid) change(rs(o), mid + 1, r, x, y);   
}

void query(int o, int l, int r, int x) {
    if(l == r) { up(a[x], tag[o]); tag[o] = 0; return ; }
    down(o);
    if(x <= mid) query(ls(o), l, mid, x);
    if(x > mid) query(rs(o), mid + 1, r, x);
}

void dfsl(int now, int to, int sum, int k) {
    if(p) return ;
    if(now == to + 1) {
        if(k) {
            if(!sum) p = 1; //如果找到一个非空集合的总和为0, 说明这个集合可以分为两个总和相同的集合
            else if(!vis[sum] && sum >= 0) vis[sta[++ cnt] = sum] = 1; // 存一下出现过的数,在下次dfs里找有没有这些数。
        }
        return ;
    }
    dfsl(now + 1, to, sum, k);
    dfsl(now + 1, to, sum + a[now] + 1, 1);
    dfsl(now + 1, to, sum - a[now] - 1, 1);
}

void dfsr(int now, int to, int sum, int k) {
    if(p) return ;
    if(now == to + 1) {
        if(k) {
            if(!sum) p = 1;
            else if(vis[sum] && sum >= 0) p = 1;
        }
        return ;
    }
    dfsr(now + 1, to, sum, k);
    dfsr(now + 1, to, sum + a[now] + 1, 1); //这里加一减一是因为题面说每个小朋友x的花费是a[x] + 1
    dfsr(now + 1, to, sum - a[now] - 1, 1); 
}

int main() {

    n = read(); m = read(); v = read();
    for(int i = 1;i <= n; i++) a[i] = read();
    make_pre();
    for(int i = 1;i <= m; i++) {
        opt = read(); l = read(); r = read();
        if(opt == 2) { change(1, 1, n, l, r); }
        else {
            int len = r - l + 1;
            if(pow(2, len) > len * v - len + 1) { printf("Yes\n"); continue; }
            else {
                for(int j = l;j <= r; j++) query(1, 1, n, j);
                p = cnt = 0; 
                dfsl(l, mid, 0, 0); dfsr(mid + 1, r, 0, 0);
                for(int j = 1;j <= cnt; j++) vis[sta[j]] = 0;
                if(p) printf("Yes\n"); else printf("No\n");
            }
        }
    }

    fclose(stdin); fclose(stdout);
    return 0;
}
posted @ 2020-10-12 09:15  C锥  阅读(181)  评论(1编辑  收藏  举报