zoukankan      html  css  js  c++  java
  • 【bzoj5110】Yazid的新生舞会

    这里是 $THUWC$ 选拔时间

    模拟赛的时候犯 $SB$ 了,写了所有的部分分,然后直接跑过了 $4$ 个大样例(一个大样例是一个特殊情况)……

    我还以为我非常叼,部分分都写对了,于是就不管了……

    离考试结束还有 $10$ 分钟的时候才发现……程序跑暴力的条件写的是 $$if(n<=5000)space force::solve();$$

    由于 $4$ 个大样例的 $n$ 都只有几百,我之前测的全是暴力……

    然后赶紧改改改,测了三个部分分的程序,都小错不断……

    于是最后调不完了,自闭 -_-。当做是一次教训吧。

    题解

    这道题的部分分给得很足,所以这里都说一下。

    另外,我在下文直接把“新生舞会的数”简称为“众数”,虽然两者定义不一样,但我想少打点字

    type=1

    只要区间内 $0,1$ 的数量不相等,就一定有一个众数($type=3$ 的部分会提到这叫“绝对众数”)。

    所以把 $1$ 看成 $1$,$0$ 看成 $-1$,就变成问有多少对不相等的前缀和。

    把所有前缀和桶排后随便乘一乘即可。

    type=2

    区间的众数不会出现超过 $15$ 次,也就是说满足要求的区间长度不会超过 $29$。

    暴力枚举所有长度为 $1$ 到 $29$ 的区间即可。

    type=3

    此时只有 $8$ 种众数。

    由于一个区间内只有一个众数(绝对众数),所以我们可以枚举众数,然后找这个数是哪些区间的众数。

    当设众数为 $x$ 时,把 $x$ 记为 $1$,其它数记为 $-1$,那么就是要找所有和 $ge 1$ 的区间。

    改成线性推:假设第 $1$ 到 $i$ 位的和为 $y$(也就是第 $i$ 位的前缀和),那就要快速查找前面有多少和 $lt y$ 的前缀。

    这就是线段树维护前缀和的裸题了。

      1 #include<bits/stdc++.h>
      2 #define N 500005
      3 #define ll long long
      4 #define rep(i,x,y) for(int i=x;i<=y;++i)
      5 #define lc o<<1
      6 #define rc o<<1|1
      7 using namespace std;
      8 inline int read(){
      9     int x=0; bool f=1; char c=getchar();
     10     for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
     11     for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
     12     if(f) return x;
     13     return 0-x;
     14 }
     15 int n,type,a[N];
     16 namespace force{
     17     ll ans;
     18     int cnt[2001][2001];
     19     void solve(){
     20         rep(i,1,n){
     21             int mx=0;
     22             for(int j=0;i+j<=n;++j){
     23                 mx=max(mx,++cnt[i][a[i+j]]);
     24                 if(mx*2>j+1) ++ans;
     25             }
     26         }
     27         cout<<ans<<endl;
     28     }
     29 };
     30 namespace type2{
     31     ll ans;
     32     struct Tree{int siz; bool clr;}tr[N<<2];
     33     int v;
     34     inline void pushdown(int o){
     35         if(tr[o].clr)
     36             tr[lc].siz=tr[rc].siz=0,
     37             tr[lc].clr=tr[rc].clr=1,
     38             tr[o].clr=0;
     39     }
     40     int ins(int o,int l,int r){
     41         ++tr[o].siz;
     42         if(l==r) return tr[o].siz;
     43         pushdown(o);
     44         int mid=(l+r)>>1;
     45         if(v<=mid) return ins(lc,l,mid);
     46         else return ins(rc,mid+1,r);
     47     }
     48     void solve(){
     49         rep(i,1,n){
     50             tr[1].siz=0, tr[1].clr=1;
     51             int mx=0;
     52             for(int j=0; j<29 && i+j<=n; ++j){
     53                 v=a[i+j];
     54                 mx=max(mx,ins(1,0,n-1));
     55                 if(mx*2>j+1) ++ans;
     56             }
     57         }
     58         cout<<ans<<endl;
     59     }
     60 };
     61 namespace type3{
     62     ll ans;
     63     int L=-500000,R,v;
     64     struct Tree{
     65         int siz[N<<2];
     66         void ins(int o,int l,int r){
     67             ++siz[o];
     68             if(l==r) return;
     69             int mid=(l+r)>>1;
     70             if(v<=mid) ins(lc,l,mid);
     71             else ins(rc,mid+1,r);
     72         }
     73         int query(int o,int l,int r){
     74             if(L<=l && r<=R) return siz[o];
     75             int mid=(l+r)>>1, ret=0;
     76             if(L<=mid) ret+=query(lc,l,mid);
     77             if(R>mid) ret+=query(rc,mid+1,r);
     78             return ret;
     79         }
     80     }tr[8];
     81     
     82     int sum[8];
     83     void solve(){
     84         rep(i,0,7) v=0, tr[i].ins(1,-500000,500000);
     85         rep(i,1,n){
     86             rep(j,0,7) --sum[j];
     87             sum[a[i]]+=2;
     88             rep(j,0,7){
     89                 R=sum[j]-1;
     90                 if(L<=R) ans+=tr[j].query(1,-500000,500000);
     91                 v=sum[j];
     92                 tr[j].ins(1,-500000,500000);
     93             }
     94         }
     95         cout<<ans<<endl;
     96     }
     97 };
     98 int main(){
     99     n=read(),type=read();
    100     rep(i,1,n) a[i]=read();
    101     if(type==0) force::solve();
    102     else if(type==1 || type==3) type3::solve();
    103     else type2::solve();
    104     return 0;
    105 }
    三个部分分

    100pts

    1.$O(n imes log^{2}n)$

    看到这种统计区间数的问题,考虑不沿用部分分的方法,而分治处理。

    然后对于一个区间 $[l,r]$,我们只需要统计经过其中点 $mid$ 的满足条件的区间数,未经过中点的区间都在两边,可以分治下去解决。

    那枚举哪些数为众数呢?

    不难发现,如果一个数是某个经过中点 $mid$ 的区间的众数的话,把该区间沿中点拆成两半,这个数至少也是一半区间的众数。

    所以我们可以预处理出从中点出发、往左和往右延伸区间,找到所有可能的众数(当然它们要不同)。

    然后有人可能认为:这样不是暴力取众数吗?不同的众数的数量级别不可能是 $O(n)$ 的吗?

    实际上这样找的话,假设区间长度是 $len$,不同的众数最多有 $O(log(len))$ 个。也就是说量级是 $log$ 级别的。

    $why???$

    我们考虑最坏情况,也就是说往其中一边延伸的区间中,所有数都是众数。

    由于这里“众数”的特殊定义,区间越长,众数所需要出现的次数就越多。

    那需要出现多少次呢?

    根据题意推理,区间延伸的长度为 $1$ 时,众数的出现次数要大于 $0$;延伸的长度为 $2$ 时,众数的出现次数要大于 $1$;延伸的长度为 $4$ 时,众数的出现次数要大于 $2$;延伸的长度为 $8$ 时,众数的出现次数要大于 $4$……

    把所有要求都 $-1$(可以证明这依然满足众数过半的条件),就变成了:区间延伸的长度为 $0$ 时,众数的出现次数要要至少为 $0$;延伸的长度为 $1$ 时,众数的出现次数要至少为 $1$;延伸的长度为 $3$ 时,众数的出现次数要至少为 $2$;延伸的长度为 $7$ 时,众数的出现次数要至少为 $4$……

    有没有发现这规律跟二进制位很像?

    比如说,这样的一个序列 $$1space 2space 2space 3space 3space 3space 3space 4space 4space 4space 4space 4space 4space 4space 4space 5...$$ 它刚好卡着数量要求,使所有数都是众数。

    需求量以这样的增长速度,不同的众数显然只有 $log$ 级别个了。

    这是最坏情况,即区间内所有的数都是众数。把这个序列任意改数,不同的众数只会少不会多。可以意会一下,具体证明感觉说不清楚。

    而该这个序列的数能组成其它任意情况的序列,所以不同的众数的数量最多只有 $log$ 级别。

    之前说过,每个区间只有一个绝对众数,所以可以独立考虑每个众数是哪些区间的众数。

    于是枚举每个众数,然后考虑怎么快速计数。

    有了归并排序的经验(其实并没什么关系),很容易写出判定式子。对于一个区间 $[l,r]$,设 $cnt_l,cnt_r$ 分别表示当前众数在区间 $[l,mid]$ 和区间 $[mid+1,r]$ 的出现次数,则当满足 $$r-l+1lt (cnt_l+cnt_r) imes 2$$ 时,当前枚举的众数是这个区间的众数,即对答案有 $1$ 的贡献。

    移项得到 $$r-cnt_r imes 2+1lt l+cnt_l imes 2$$,方便按位维护。

    所以枚举经过中点 $mid$ 的区间的右端点 $r$ 时,其实就是找一个与左端点 $l$ 相关的后缀和(有多少个数比某个值大)。遍历左半区间的左端点 $l$ 后,推一次后缀和就行了。(后缀和……就是反向前缀和)

    啥?暴力推后缀和?后缀和的范围最大是多少?

    显然, $l$ 和 $cnt$ 都是 $n$ 级别的,所以 $l+cnt imes 2$ 的范围最大是 $3n$ 级别的。

    分治的层数复杂度是 $O(log(n))$,每层中枚举众数的复杂度是 $O(log(n))$(其实比这个大,但层数越小越趋近于这个),每层在左半区间遍历左端点 $l$ 加上在右半区间遍历右端点 $r$ 的复杂度是 $O(n)$,总复杂度是 $O(n imes log^2(n))$。

    这里说明一下,$l+cnt imes 2$ 有可能小于 $0$,但我写的代码直接存在了负数下标位,理论上这样可能会出事(负数下标的指针有可能指向其它数组,然后导致指向的数组被无端改了数),不过我交上去过了就没管了……建议把这个值统一加个常数,存在自然数位,保证不会出事。

     1 #include<bits/stdc++.h>
     2 #define rep(i,x,y) for(int i=x;i<=y;++i)
     3 #define dwn(i,x,y) for(int i=x;i>=y;--i)
     4 #define ll long long
     5 #define N 500001
     6 #define inf 2147483647
     7 using namespace std;
     8 inline int read(){
     9     int x=0; bool f=1; char c=getchar();
    10     for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
    11     for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
    12     if(f) return x;
    13     return 0-x;
    14 }
    15 int n,type,a[N]; ll ans;
    16 int lsh[N],cnt[N*3],sum[N];
    17 bool vis[N];
    18 void solve(int l,int r){
    19     if(l==r){++ans; return;}
    20     int mid=(l+r)>>1;
    21     solve(l,mid), solve(mid+1,r);
    22     int m=0;
    23     dwn(i,mid,l){
    24         if((++cnt[a[i]])*2>mid-i+1)
    25             if(!vis[a[i]]) vis[a[i]]=1, lsh[++m]=a[i];
    26     }
    27     dwn(i,mid,l)
    28         cnt[a[i]]=0;
    29     rep(i,mid+1,r){
    30         if((++cnt[a[i]])*2>i-mid)
    31             if(!vis[a[i]]) vis[a[i]]=1, lsh[++m]=a[i];
    32     }
    33     rep(i,mid+1,r)
    34         cnt[a[i]]=0;
    35     rep(i,1,m){
    36         //printf("yes:%d
    ",lsh[i]);
    37         int sum=0,mn=inf,mx=-inf;
    38         dwn(j,mid,l){
    39             if(lsh[i]==a[j]) ++sum;
    40             ++cnt[j+sum*2];
    41             //printf("%d
    ",j+sum*2); 
    42             mx=max(mx,j+sum*2), mn=min(mn,j+sum*2);
    43         }
    44         dwn(j,mx-1,mn) cnt[j]+=cnt[j+1];
    45         sum=0; int x;
    46         //printf("que:"); rep(j,mn,mx) printf("%d ",cnt[j]); putchar('
    '); 
    47         rep(j,mid+1,r){
    48             if(lsh[i]==a[j]) ++sum;
    49             //printf("%d:%d
    ",j,j-sum*2+2);
    50             x=j-sum*2+2; if(x>mx) continue; if(x<mn) x=mn;
    51             ans+=cnt[x]; //printf("%d
    ",ans);
    52         }
    53         //printf("faq:%d
    ",ans);
    54         vis[lsh[i]]=0;
    55         rep(j,mn,mx) cnt[j]=0;
    56     }
    57     //printf("
    %d %d %d
    ",l,r,ans);
    58 }
    59 int main(){
    60     n=read(),type=read();
    61     rep(i,1,n) a[i]=read();
    62     solve(1,n);
    63     cout<<ans<<endl;
    64     return 0;
    65 }
    66 /*
    67 20 0
    68 163 29 29 135 29 29 50 29 85 44 85 135 241 135 135 135 50 50 50 34
    69 7 0
    70 1 1 1 2 2 2 3
    71 */
    View Code

    2.$O(n imes log(n))$

    考虑优化 $type=3$ 的做法。

    本来我们要对每一种众数都开一棵线段树,维护把其看成 $1$、把其它数看成 $-1$ 时的每个前缀和,但如果对序列的数没有限制的话这样显然炸了。

    我们考虑一下为什么会炸。

    其实就是状态太多了,最多有 $50w imes 50w$ 大小呢。

    可序列里最多只有 $50w$ 个数啊!好像每个数都不会出现很多次。

    对于一个没出现很多次的数,它所对应的序列 好像会出现很多 $-1$?

    然后就会发现,我们把众数看成 $1$,其它数看成 $-1$ 后,序列的 $50w imes 50w$ 个数中,只有 $50w$ 个是 $1$,其它一大堆都是 $-1$。

    也就是说前缀和连续下降的频率很高。

    画个图意会一下。这张图反映了 设一个数为众数时,所有前缀和的变化趋势。横坐标是位置,纵坐标是这位的前缀和。

    我们是否可以直接用一次函数维护这些斜率为 $-1$ 的前缀和变化线?

    肯定可以,因为 $n$ 卡满时,$50w$ 个线段树总共 $50w$ 个位置的斜率是 $1$,所以我们可以暴力枚举线段树中的这些位置,然后把它与当前线段树中上一个 斜率为 $1$ 的位置连一条斜率为 $-1$ 的一次函数。

    还有,暴力枚举那些斜率为 $1$ 的小段时,还可以顺便把那上面的点的贡献都算了。

    贡献就是这个点左边有多少个点在它下边($type=3$ 的部分说过,要找所有和 $ge 1$ 的区间,也就是说对于一个前缀,要找它前面有多少个比它小的前缀)。

    从左往右扫这些斜率为 $1$ 的小段时,维护一个 $y$ 轴的至于线段树就行了。

    现在就剩下这样的问题:

    1. 怎么插入一次函数;

    2. 斜率为 $-1$ 的极大线段的贡献怎么算(刚才只说了斜率为 $1$ 的部分)

    插入一次函数比较简单,设要插入的斜率为 $-1$ 的极大线段的上下端 $y$ 坐标分别为 $l$ 和 $r$,把值域线段树的 $[l,r]$ 区间加 $1$ 即可。

    然后斜率为 $-1$ 的段的贡献嘛……

    首先,我们要找的是关于位置和前缀和的同序对,所以对于斜率为 $-1$ 的线段中的任意一点,这段线段没有点可以对它造成贡献。

    所以我们只考虑插入这条线段之前的线段树。

    然后会发现,这条线段的贡献(统计量) 是插入这条线段之前的线段树的一个一次函数。

    什么意思?画个图。

    图中的“几次”表示其对应的 $y$ 轴的所有点被统计了几次。

    这样统计的一次函数,纵坐标 $y$ 每 $+1$,该纵坐标上的点的被统计次数就 $-1$,显然是个一次函数。这个还比较好弄,原来我们的值域线段树只记每个纵坐标 $y$ 上有多少个点(假设用 $cnt$ 维护),现在再维护一个 $cnt imes i$ 就行了($i$ 表示纵坐标),做点减法即可把系数 $i$ 变成 $-i$,就是我们所需要的斜率了。

    这是 $SYF$ 的代码(我并没补这种)

      1 #include<algorithm>
      2 #include<cmath>
      3 #include<complex>
      4 #include<cstdio>
      5 #include<cstdlib>
      6 #include<cstring>
      7 #include<ctime>
      8 #include<iomanip>
      9 #include<iostream>
     10 #include<map>
     11 #include<queue>
     12 #include<set>
     13 #include<stack>
     14 #include<vector>
     15 #define rep(i,x,y) for(register int i=(x);i<=(y);++i)
     16 #define dwn(i,x,y) for(register int i=(x);i>=(y);--i)
     17 #define LL long long
     18 #define maxn 500010 
     19 #define ls son[u][0]
     20 #define rs son[u][1]
     21 #define mi ((l+r)>>1)
     22 using namespace std;
     23 int read()
     24 {
     25     int x=0,f=1;char ch=getchar();
     26     while(!isdigit(ch)&&ch!='-')ch=getchar();
     27     if(ch=='-')f=-1,ch=getchar();
     28     while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
     29     return x*f;
     30 }
     31 void write(LL x)
     32 {
     33     if(x==0){putchar('0'),putchar('
    ');return;}
     34     int f=0;char ch[20];
     35     if(x<0)putchar('-'),x=-x;
     36     while(x)ch[++f]=x%10+'0',x/=10;
     37     while(f)putchar(ch[f--]);
     38     putchar('
    ');
     39     return;
     40 }
     41 vector<int>v[maxn];
     42 LL tr[maxn*21],ans;
     43 int n,a[maxn],s[maxn],rt,son[maxn*21][2],mkk[maxn*21],mkb[maxn*21],nd;
     44 void mark(int u,int l,int r,int k,int b){mkk[u]+=k,mkb[u]+=b,tr[u]+=(LL)(b+(LL)(r-l)*(LL)k+b)*(LL)(r-l+1)/2ll;}
     45 void pd(int u,int l,int r)
     46 {
     47     if(l<r&&(mkk[u]||mkb[u]))
     48     {
     49         if(!ls)ls=++nd;
     50         mark(ls,l,mi,mkk[u],mkb[u]);
     51         if(!rs)rs=++nd;
     52         mark(rs,mi+1,r,mkk[u],mkb[u]+(mi+1-l)*mkk[u]);
     53         mkk[u]=mkb[u]=0;
     54     }
     55 }
     56 void pu(int u){tr[u]=tr[ls]+tr[rs];}
     57 void add(int u,int l,int r,int x,int y,int k,int b)
     58 {
     59     //cout<<l<<" "<<r<<endl;
     60     if(x<=l&&r<=y){/*cout<<"k:"<<k<<" b:"<<b<<" l:"<<l<<" r:"<<r<<endl;*/return mark(u,l,r,k,b);}
     61     pd(u,l,r);
     62     if(x<=mi)
     63     {
     64         if(!ls)ls=++nd;
     65         add(ls,l,mi,x,y,k,b);
     66     }
     67     if(y>mi)
     68     {
     69         if(!rs)rs=++nd;
     70         if(x<=mi)add(rs,mi+1,r,mi+1,y,k,b+(mi+1-x)*k);
     71         else add(rs,mi+1,r,x,y,k,b);
     72     }
     73     return pu(u);
     74 }
     75 LL ask(int u,int l,int r,int x,int y)
     76 {
     77     //cout<<"ask l:"<<l<<" r:"<<r<<" mk:"<<mkk[u]<<" "<<mkb[u]<<endl;
     78     if(x<=l&&r<=y)return tr[u];
     79     pd(u,l,r);
     80     LL res=0;
     81     if(x<=mi&&ls)res=ask(ls,l,mi,x,y);
     82     if(y>mi&&rs)res+=ask(rs,mi+1,r,x,y);
     83     return res;
     84 }
     85 int main()
     86 {
     87     n=read();read();
     88     rep(i,1,n)a[i]=read(),v[a[i]].push_back(i);
     89     rep(i,0,n-1)
     90     {
     91         v[i].push_back(n+1);
     92         int lim=v[i].size(),pre=0;rt=nd=1;
     93         rep(j,0,lim-1)
     94         {
     95             s[v[i][j]]=s[pre]-v[i][j]+pre+2;
     96             LL tmp=ask(rt,-(n+5),n+5,s[v[i][j]]-1,s[pre]);
     97             ans+=tmp;
     98             add(rt,-(n+5),n+5,s[pre]+1,n+5,0,v[i][j]-pre);
     99             if(v[i][j]>pre+1)add(rt,-(n+5),n+5,s[v[i][j]],s[pre],1,1);
    100             pre=v[i][j];
    101         }
    102         rep(j,1,nd)mkk[j]=mkb[j]=son[j][0]=son[j][1]=tr[j]=0;
    103     }
    104     /*rep(i,1,n)
    105     {
    106         s[i]=s[lst[a[i]]]-i+lst[a[i]]+2;
    107         LL tmp=
    108         if(nd>=(maxn<<5)){cout<<"ooooh"<<endl;return 0;}
    109         if(lst[a[i]]>i){cout<<"nooo";return 0;}
    110         //cout<<i<<" "<<lst[a[i]]<<" "<<s[i]<<" "<<tmp<<endl;
    111         ans+=tmp;
    112         //cout<<"opl:"<<s[lst[a[i]]]+1<<" opr:"<<n+5<<endl;
    113     rep(j,0,n-1)
    114         {
    115             cout<<"num:"<<j<<endl;
    116             rep(k,-(n+5),n+5)cout<<k<<" "<<ask(rt[j],-(n+5),n+5,k,k)<<endl;
    117         }
    118     }*/
    119     //rep(i,0,n-1)ans+=ask(rt[i],-(n+5),n+5,s[lst[i]]-n+lst[i],s[lst[i]]);
    120     write(ans);
    121     return 0;
    122 }
    View Code

    想看另一种线段树方法(只记众数的出现次数,不记其它数为 $-1$)的 点 这 里

    3.$O(n)$

    这个吧,我也没明白捏 _^_

  • 相关阅读:
    《C++ Primer Plus》15.1 友元 学习笔记
    《C++ Primer Plus》14.4 类模板 学习笔记
    《C++ Primer Plus》14.3 多重继承 学习笔记
    《C++ Primer Plus》14.2 私有继承 学习笔记
    《C++ Primer Plus》第13章 类继承 笔记
    继承和动态内存分配——需要为继承类定义 显式析构函数、复制构造函数和赋值运算符
    C++中的抽象基类示例
    C++ 在继承中使用virtual
    《C++ Primer Plus》第12章 类和动态内存分配 学习笔记
    《C++ Primer Plus》12.7 队列模拟 学习笔记
  • 原文地址:https://www.cnblogs.com/scx2015noip-as-php/p/bzoj5110.html
Copyright © 2011-2022 走看看