• 洛谷 P4062


    题面传送门

    题意:
    给出一个序列 (a),求 (a) 有多少个子区间 ([l,r]),满足这个区间中出现次数最多的数出现次数 (>dfrac{r-l+1}{2})
    (1 leq n leq 5 imes 10^5)

    首先肯定要枚举出现次数最多的数是什么,假设为 (x)
    记序列中为 (x) 的数为 (+1),数列中不为 (x) 的数为 (-1),那么 (x) 出现次数 (>dfrac{r-l+1}{2}) 等价于该区间中对应的数的和 (>0)
    考虑对这个 (+1,-1) 的序列做一遍前缀和得到 (s_i),那么满足条件的区间个数即为 (s_i)顺序对个数
    对于 (type=1,3) 的情况,做 (8) 次树状数组求顺序对就可以了。

    然而对于原题来说这样肯定是不行的,不过发现对于 (x) 取什么值,(1) 的个数加起来只有 (n) 个,这意味着大部分数都是 (-1),那么我们思考能不能拿这个性质做文章呢?
    考虑从左到右依次插入一段连续的 (-1),显然这些位置的 (s) 值可以形成一段连续的区间(公差为 (-1) 的等差数列),不妨设其为 ([L,R])
    假设 (cnt_j) 为当前 (s_i=j)(i) 的个数,那么这段区间的贡献就是

    [sumlimits_{i=L}^{R}sumlimits_{j=-infty}^{i-1}cnt_j ]

    把这个式子稍微调整一下就可以得到

    [(R-L+1) imessumlimits_{j=-infty}^{L-1}cnt_j+sumlimits_{j=L}^Rcnt_j imes(R-j) ]

    是不是感觉有亿点点可维护?
    线段树维护 (cnt_j) 的值,支持区间加、求区间 (cnt_i) 的和,以及区间 (i imes cnt_i) 的和,就可以在 (mathcal O(log n)) 的时间内求出上面那个式子的值。
    由于 (s_j) 可能 (<0),所以下标要整体加上一个值。
    虽然 (-1) 的个数很多,但是连续的 (-1) 段的个数是 (mathcal O(n)) 级别的,而我们恰好利用了这个性质将复杂度降了下来。

    #include <bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define fz(i,a,b) for(int i=a;i<=b;i++)
    #define fd(i,a,b) for(int i=a;i>=b;i--)
    #define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
    #define fill0(a) memset(a,0,sizeof(a))
    #define fill1(a) memset(a,-1,sizeof(a))
    #define fillbig(a) memset(a,63,sizeof(a))
    #define pb push_back
    #define ppb pop_back
    #define mp make_pair
    typedef pair<int,int> pii;
    typedef long long ll;
    const int MAXN=5e5+5;
    int n,a[MAXN];
    vector<int> v[MAXN];
    struct node{
    	int l,r;
    	ll val,mul,lz,sumi;
    } s[MAXN<<4];
    void build(int k,int l,int r){
    	s[k].l=l;s[k].r=r;s[k].val=s[k].lz=s[k].mul=0;if(l==r){s[k].sumi=l-MAXN;return;}
    	int mid=(l+r)>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
    	s[k].sumi=s[k<<1].sumi+s[k<<1|1].sumi;
    }
    void pushdown(int k){
    	if(s[k].lz){
    		s[k<<1].val+=(s[k<<1].r-s[k<<1].l+1)*s[k].lz;
    		s[k<<1].mul+=s[k<<1].sumi*s[k].lz;s[k<<1].lz+=s[k].lz;
    		s[k<<1|1].val+=(s[k<<1|1].r-s[k<<1|1].l+1)*s[k].lz;
    		s[k<<1|1].mul+=s[k<<1|1].sumi*s[k].lz;s[k<<1|1].lz+=s[k].lz;
    		s[k].lz=0;
    	}
    }
    void modify(int k,int l,int r,int x){
    	if(l<=s[k].l&&s[k].r<=r){
    		s[k].val+=(s[k].r-s[k].l+1)*x;
    		s[k].mul+=s[k].sumi*x;s[k].lz+=x;
    		return;
    	} pushdown(k);
    	int mid=(s[k].l+s[k].r)>>1;
    	if(r<=mid) modify(k<<1,l,r,x);
    	else if(l>mid) modify(k<<1|1,l,r,x);
    	else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
    	s[k].val=s[k<<1].val+s[k<<1|1].val;
    	s[k].mul=s[k<<1].mul+s[k<<1|1].mul;
    }
    ll query(int k,int l,int r){
    //	printf("%d %d %d
    ",k,l,r);
    	if(l<=s[k].l&&s[k].r<=r) return s[k].val;
    	pushdown(k);int mid=(s[k].l+s[k].r)>>1;
    	if(r<=mid) return query(k<<1,l,r);
    	else if(l>mid) return query(k<<1|1,l,r);
    	else return query(k<<1,l,mid)+query(k<<1|1,mid+1,r);
    }
    ll queryi(int k,int l,int r){
    	if(l<=s[k].l&&s[k].r<=r) return s[k].mul;
    	pushdown(k);int mid=(s[k].l+s[k].r)>>1;
    	if(r<=mid) return queryi(k<<1,l,r);
    	else if(l>mid) return queryi(k<<1|1,l,r);
    	else return queryi(k<<1,l,mid)+queryi(k<<1|1,mid+1,r);
    }
    int main(){
    	int qwq;scanf("%d%d",&n,&qwq);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]),v[a[i]].pb(i);
    	build(1,0,MAXN<<1);ll ret=0;
    	for(int i=0;i<n;i++){
    //		printf("%d
    ",i);
    		int pre=0,sum=0;modify(1,MAXN,MAXN,1);
    		for(int j=0;j<v[i].size();j++){
    			int cur=v[i][j];
    			if(pre+1!=cur){
    				int r=sum-1,l=sum-(cur-pre-1);
    				ret+=r*query(1,l+MAXN,r+MAXN)-queryi(1,l+MAXN,r+MAXN);
    				ret+=(r-l+1)*query(1,0,l-1+MAXN);modify(1,l+MAXN,r+MAXN,1);
    				sum-=(cur-pre-1);
    			}
    			sum++;ret+=query(1,0,sum-1+MAXN);modify(1,sum+MAXN,sum+MAXN,1);
    			pre=cur;
    		}
    		if(pre!=n){
    			int r=sum-1,l=sum-(n-pre);
    			ret+=r*query(1,l+MAXN,r+MAXN)-queryi(1,l+MAXN,r+MAXN);
    			ret+=(r-l+1)*query(1,0,l-1+MAXN);modify(1,l+MAXN,r+MAXN,1);
    			sum-=(n-pre);
    		}
    		pre=0,sum=0;modify(1,MAXN,MAXN,-1);
    		for(int j=0;j<v[i].size();j++){
    			int cur=v[i][j];
    			if(pre+1!=cur){
    				int r=sum-1,l=sum-(cur-pre-1);
    				modify(1,l+MAXN,r+MAXN,-1);
    				sum-=(cur-pre-1);
    			}
    			sum++;modify(1,sum+MAXN,sum+MAXN,-1);
    			pre=cur;
    		}
    		if(pre!=n){
    			int r=sum-1,l=sum-(n-pre);
    			modify(1,l+MAXN,r+MAXN,-1);
    			sum-=(n-pre);
    		}
    	}
    	printf("%lld
    ",ret);
    	return 0;
    }
    

    upd on 2020.12.4:

    考场上想的做法竟然过了!incredible!我还以为它过不了呢/xyx
    讲一个 (nsqrt{nlog n}) 的做法。
    考虑分块,设一个临界值 (B)
    对于每个出现次数 (leq B) 的数,显然它只能对长度 (<2B) 的区间产生贡献,枚举每个长度 (<2B) 的区间。
    对于每个出现次数 (>B) 的数,这样的数顶多 (dfrac{n}{B}) 个,对于每一个这样的数搞一遍树状数组求顺序对。
    时间复杂度 (2nB+dfrac{n^2log n}{B}),根据均值不等式可以算得复杂度最优为 (nsqrt{nlog n})
    woc 这玩意儿真的 nb (10^9) 左右给我跑过去了。
    顺便提一句:考场上我在求 (<2B) 的区间的贡献的时候用了 memset 所以 T 掉了还以为是算法本身的锅。

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN=5e5+5;
    typedef long long ll;
    int n,sub,a[MAXN];
    const int BLK=800;
    int cnt[MAXN],f[MAXN];
    int sum[MAXN];
    int bit[MAXN<<1];
    void add(int x,int v){for(int i=x;i<MAXN+MAXN;i+=(i&(-i))) bit[i]+=v;}
    int query(int x){int ret=0;for(int i=x;i;i-=(i&(-i))) ret+=bit[i];return ret;}
    int solve(){
    	for(int i=1;i<=n;i++) f[a[i]]++;
    	ll ret=0;
    	for(int i=1;i<=n;i++){
    		int mx=0,pos=0;
    		for(int j=i;j<=n&&j<=i+BLK*2;j++){
    			cnt[a[j]]++;if(cnt[a[j]]>mx) mx=cnt[a[j]],pos=a[j];
    			if(mx>(j-i+1)/2&&f[pos]<BLK) ret++;
            }
            for(int j=i;j<=n&&j<=i+BLK*2;j++){
    			cnt[a[j]]--;
    		}
    	}
    	for(int i=0;i<n;i++){
    		if(f[i]<BLK) continue;
    		memset(sum,0,sizeof(sum));
    		for(int j=1;j<=n;j++){
    			if(a[j]==i) sum[j]=sum[j-1]+1;
    			else sum[j]=sum[j-1]-1;
    		}
    		memset(bit,0,sizeof(bit));
    		add(MAXN,1);
    		for(int j=1;j<=n;j++){
    			ret+=query(sum[j]-1+MAXN);
    			add(sum[j]+MAXN,1);
    		}
    	}
    	printf("%lld
    ",ret);
    	return 0;
    }
    int main(){
    	scanf("%d%d",&n,&sub);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	return solve();
    }
    

    终于把自爆的心头之恨化解掉了,爽

  • 相关阅读:
    个人作业2——英语学习APP案例分析
    结对编程1—— 基于界面的四则运算(38/39)
    个人作业1——四则运算题目生成
    软件工程实践项目课程的自我目标
    IE6/IE7/IE8/Firefox/Chrome/Safari的CSS hack兼容一览表
    微信小程序爬坑日记之蜜汁缩进
    微信小程序爬坑日记之背景图片设置
    你不知道的 js 保留字
    微信小程序爬坑日记之下拉刷新
    ES7-Es8 js代码片段
  • 原文地址:https://www.cnblogs.com/ET2006/p/luogu-P4062.html
Copyright © 2020-2023  润新知