zoukankan      html  css  js  c++  java
  • BZOJ 2989 数列

    目录

    \(\frak Description\)

    \(\rm Link.\)

    \(\frak Solution\)

    对于每个 \(x\),考虑满足 \(\text{graze}(x,i)\le k\)\(i\) 满足什么条件:容易想到将每个数搞成一个点对 \((i,a_i)\),这样 \(\text{graze}(x,i)\) 其实就是曼哈顿距离,那么合法的点 \(i\) 显然围绕点 \(x\) 形成一个正方形,只不过是旋转了 \(45^\circ\). 问题就转化成统计在这个正方形的点的数量。

    我们可以用切比雪夫距离来再转化一下,点就变成了 \((a_i+i,a_i-i)\),但同时我们又将正方形转回来了。如果想看具体推导可以看看 \(\rm details\)

    对于曼哈顿距离 \(D\) 有:

    \[\begin{align} D&=|x_1-x_2|+|y_1-y_2|\\ &=\max\{x_1-x_2+y_1-y_2,x_2-x_1+y_1-y_2,x_1-x_2+y_2-y_1,x_2-x_1+y_2-y_1\}\\ &=\max\{|(x_2+y_2)-(x_1+y_1)|,|(x_2-y_2)-(x_1-y_1)|\}\\ &=\max\{|(x_2+y_2)-(x_1+y_1)|,|(y_1-x_1)-(y_2-x_2)|\} \end{align} \]

    所以将点坐标转化成 \((x+y,x-y)\) 或者 \((y+x,y-x)\) 都可以用切比雪夫距离表示原来的 \(D\).

    如果没有强制在线,现在就可以用 \(\rm cdq\) 分治 + 扫描线做到 \(\mathcal O(n\log^2 n)\)(以下均设 \(n\) 与值域同阶). 不过如果强制在线呢?这儿有个 并不船新\(\rm trick\):二进制分组!

    二进制分组会把修改分为若干大小为 \(2\) 的幂次的组,每组用一个数据结构维护。新加入一个修改时,会根据修改个数和前面的修改进行合并(这个合并类似于二进制的进位,即,两个大小为 \(2\) 的组合并成大小为 \(4\) 的组,是不是很像 \(1024\) )。查询时在每一组的数据结构里分别查询。

    这有啥用呢?有了它,我们就可以对于每一组维护一棵主席树,合并的时候暴力重构即可。

    询问复杂度是 \(\mathcal O(q\log^2 n)\) 的,现在我们来看看合并复杂度:假设这是第 \(i\) 次修改,那么新加入的点就会和之前约 \(\text{lowbit}(i)\) 个点合并,毕竟如果将 \(i\) 减一,此时 \(\log_2(\text{lowbit}(i))\) 位之后全为 \(1\),依次合并上去就是约 \(\text{lowbit}(i)\) 个点。合并的主要复杂度在于 recycle(),此时复杂度可以表示为 \(f(2^i)=\sum_{j=0}^{i-1}2^j\cdot \log(2^j)=\sum_{j=0}^{i-1}j\cdot 2^j\).

    考虑枚举每个 \(2\) 的幂作为 \(\rm lowbit\),总合并复杂度就是:

    \[\begin{align} \sum_{i=1}^{\log n} f(2^i)\cdot \frac{n}{2^{i+1}}&=\sum_{i=1}^{\log n} \frac{n}{2^{i+1}}\cdot \sum_{j=1}^{i-1}j\cdot 2^j\\ &=\sum_{i=1}^{\log n} \frac{n}{2}\cdot \sum_{j=1}^{i-1}j\cdot \frac{1}{2^{i-j}} \end{align} \]

    欸?后面那一坨不就是高中数列的经典题吗?化一化可以得到:

    \[\begin{align} &=\sum_{i=1}^{\log n} \frac{n}{2}\cdot \left((i-1)-\sum_{j=2}^{i-1}\frac{1}{2^{i-j}}\right)\\ &<\sum_{i=1}^{\log n} {n}\cdot \log n=n\log^2 n \end{align} \]

    \(\frak Code\)

    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
    
    template <class T>
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f |= (s=='-');
    	while(s>='0' and s<='9')
    		x = (x<<1)+(x<<3)+(s^48),
    		s = getchar();
    	return f?-x:x;
    }
    
    template <class T>
    inline void write(T x) {
        static int writ[40],w_tp=0;
        if(x<0) putchar('-'),x=-x;
        do writ[++w_tp]=(x-x/10*10),x/=10; while(x);
        while(putchar(writ[w_tp--]^48),w_tp);
    }
    
    #include <vector>
    #include <algorithm>
    using namespace std;
    typedef pair <int,int> pii;
    
    const int B = 6e4+2;
    const int lim = 2e5;
    const int maxn = 6e4+5;
    const int SIZE = maxn*40;
    
    int n,q,a[maxn],rec[SIZE],tp,idx;
    int rt[20][(lim>>1)+5],tail;
    struct node {
        bool ban; int siz,son[2];
    } t[SIZE];
    vector <pii> v[20];
    
    void recycle(int o) {
        if(t[o].ban || !o) return;
        rec[++ tp]=o, t[o].ban=1;
        // 因为主席树节点可能有很多爸爸,所以不妨直接将节点状态设置成 "ban" 而不是取消它与爸爸的关系
        recycle(t[o].son[0]);
        recycle(t[o].son[1]);
    }
    
    int NewNode(int p) {
        int x = tp?rec[tp--]:++idx;
        t[x] = t[p]; t[x].ban = false;
        return x;
    }
    
    void modify(int &o,int p,int l,int r,int pos) {
        o=NewNode(p); ++ t[o].siz;
        if(l==r) return;
        int mid = l+r>>1;
        if(pos<=mid) modify(t[o].son[0],t[p].son[0],l,mid,pos);
        else modify(t[o].son[1],t[p].son[1],mid+1,r,pos);
    }
    
    void rebuild(int x) {
        sort(v[x].begin(),v[x].end());
        for(int i=0; i<v[x].size(); ++i)
            modify(rt[x][i],i?rt[x][i-1]:0,1,lim,v[x][i].second);
    }
    
    void ins(int x,int y) {
        vector <pii> t; 
        t.push_back(make_pair(x,y));
        while(tail && v[tail].size()==t.size()) {
            for(int i=0; i<v[tail].size(); ++i)
                t.push_back(v[tail][i]),
                recycle(rt[tail][i]);
            -- tail;
        }
        v[++ tail] = t;
        rebuild(tail);
    }
    
    int query(int a,int b,int l,int r,int L,int R) {
        if(!b) return 0;
        if(l>=L && r<=R) return t[b].siz-t[a].siz;
        int mid = l+r>>1, ans=0;
        if(L<=mid) ans = query(t[a].son[0],t[b].son[0],l,mid,L,R);
        if(R>mid) ans += query(t[a].son[1],t[b].son[1],mid+1,r,L,R);
        return ans;
    }
    
    int ask(int x,int y,int l) {
        int ans=0;
        for(int i=1;i<=tail;++i) {
            int L=lower_bound(v[i].begin(),v[i].end(),make_pair(x-l,0))-v[i].begin()-1;
            int R=lower_bound(v[i].begin(),v[i].end(),make_pair(x+l+1,0))-v[i].begin()-1;
            ans += query(~L?rt[i][L]:0,~R?rt[i][R]:0,1,lim,y-l,y+l);
        }
        return ans;
    }
    
    int main() {
        n=read(9), q=read(9);
        for(int i=1;i<=n;++i) 
            a[i]=read(9), ins(a[i]+i,a[i]-i+B);
        char opt[10]; int x,y;
        while(q --) {
            scanf("%s %d %d",opt+1,&x,&y);
            if(opt[1]=='M') {
                a[x] = y;
                ins(a[x]+x,a[x]-x+B);
            } else
                print(ask(a[x]+x,a[x]-x+B,y),'\n');
        }
        return 0;
    }
    
    
  • 相关阅读:
    windows cmd中查看某个命令所在的路径
    linux vi编辑器中,如何通过快捷键上下翻页?
    linux系统中,查看当前系统中,都在监听哪些端口
    下载mysql server安装包的时候,不登录oracle账号,实现下载
    plsql developer中,清除登录历史
    linux环境下,清空history中记录的历史命令
    Linux下搭建hadoop开发环境-超详细
    HDFS架构详解-非官档
    SSH免密码登录配置方法详解
    bin/hdfs dfs命令存在WARN util.NativeCodeLoader问题消除方法
  • 原文地址:https://www.cnblogs.com/AWhiteWall/p/12356059.html
Copyright © 2011-2022 走看看