• 洛谷P4062 [Code+#1]Yazid 的新生舞会(树状数组)


    考虑数字x的贡献

    设S[i]表示x在前i个位置的出现次数

    那么如果存在区间[l+1,r]满足要求,则S[r]-S[l]>(r-l)/2,即2S[r]-r>2S[l]-l

    令T[i]=2S[i]-i

    那可以得到一个n^2 * logn的做法:

    枚举数字x,枚举位置,用一个数据结构统计一段区间内<某个数的个数

     

    优化一下这个做法

    对于某个数字x,看一下它的T

    例如下例中,x=2

    位置:0  1   2   3   4   5   6   7   8   9   10  11

    数字:   1   2   5   3   2   3   1   2   4    2   1

    T :  0  -1   0  -1  -2  -1  -2  -3  -2   -3  -2  -3

    段: 1   1   2   2   2   3   3   3   4    4   5   5

    我们发现如果存在k个x,那么他把整个序列分为了k+1段,如上所示

    每一段是一个等差数列

    用a[i]表示T为i的数的出现次数

    对于某个x的所有段,按从左到右依次处理

    假设现在这一段的T值范围为[l0,r0]

    那么以这一段为右端点的区间对答案的贡献就是

     

    式子里j写负无穷表示一个足够小的边界

    然后对a中区间[l0,r0]的数加1

    我们看用什么样的数据结构能够快速完成查询和修改的操作

     

    查询里有2个∑,我们用前缀和去掉一个,用b[i]表示a的前缀和

    每一段的贡献就是,即区间求和

    那么修改操作对a中区间[l0,r0]的数加1,就是对b中区间[l0,r0]的数加首项为1,公差为1的等差数列

    这可以用线段树来完成

    时间复杂度nlogn

     

    继续想,用c[i]表示b的前缀和,即a的二阶前缀和

    那么每一段的贡献就是c[r0-1] - c[l0-2]

    区间加操作怎么做?

    区间加一个数可以想到利用差分数组变为单点修改

    令d表示a的差分数组

    现在a b c d的关系是 d是a的差分数组,a是d的前缀和,b是a的前缀和即d的二阶前缀和,c是b的前缀和即d的三阶前缀和

    把所有的下标都右移n+1位,变为正整数

     

    现在可以用树状数组维护d[k] , k*d[k] , k*k*d[k] 的前缀和

    #include<bits/stdc++.h>
    
    using namespace std;
    
    #define N 500003
    
    int nn;
    long long c0[N<<1],c1[N<<1],c2[N<<1];
    
    vector<int>v[N];
    
    int a[N],sum[N];
    
    #define lowbit(x) x&-x
    
    long long query(int x)
    {
        long long ss=0;
        for(int i=x;i;i-=lowbit(i))
            ss+=1ll*(x+1)*(x+2)*c0[i]-(2ll*x+3)*c1[i]+c2[i]; 
        return ss>>1;
    }
    
    void change(int x,int k)
    {
        for(int i=x;i<=nn;i+=lowbit(i)) 
        {
            c0[i]+=k;
            c1[i]+=k*x;
            c2[i]+=1ll*k*x*x;
        }
    }
    
    int main()
    {
        int n,x;
        long long ans=0;
        scanf("%d%*d",&n);
        for(int i=1;i<=n;++i) 
        {
            scanf("%d",&x);
            v[x].push_back(i);
        }
        int delta=n+1,y,siz,last;
        nn=2*n+1;
        for(int i=0;i<n;++i)
        {
            v[i].push_back(n+1);
            siz=v[i].size();
            last=0;
            for(int j=0;j<siz;++j)
            {
                x=2*j-last+delta;
                y=2*j-(v[i][j]-1)+delta;
                ans+=query(x-1);
                if(y>2) ans-=query(y-2);
                change(y,1);
                change(x+1,-1);
                last=v[i][j];
            }
            last=0;
            for(int j=0;j<siz;++j)
            {
                x=2*j-last+delta;
                y=2*j-(v[i][j]-1)+delta;
                change(y,-1);
                change(x+1,1);
                last=v[i][j];
            }
        }
        printf("%lld
    ",ans);
    }
    作者:xxy
    本文版权归作者和博客园共有,转载请用链接,请勿原文转载,Thanks♪(・ω・)ノ。
  • 相关阅读:
    不删除数据库,只删除GridView的某一行!
    纯CSS无图打造圆角Table 无图制作圆角
    2009年总结与2010总体计划
    工作中的碰到的问题,以及处理过程:
    SQL Server 2005 不允许远程连接解决方法
    Visual Studio 2008项目模板丢失的解决办法
    C#格式化数值结果表
    准备把csdn的博客搬到这里
    生产系统中 RAC 数据库服务器 不要批量 gzip压缩
    不再更新的业务统计表
  • 原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/15162651.html
Copyright © 2020-2023  润新知