zoukankan      html  css  js  c++  java
  • 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
    "); 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
    "); else printf("No
    ");
                }
            }
        }
    
        fclose(stdin); fclose(stdout);
        return 0;
    }
    
  • 相关阅读:
    ExIco应用程序图标保存器1.0发布
    Object.defineProperty
    es6代理 proxy 学习
    node js学习记录
    css默认值列表
    关于 keyup keydown keypress
    转载一篇关于css选择器的,很透彻
    ......图片上传
    mimi
    css 开发心得
  • 原文地址:https://www.cnblogs.com/czhui666/p/13800902.html
Copyright © 2011-2022 走看看