Subarray(思维+模拟+lazy标记+……)

37 篇文章 0 订阅
33 篇文章 0 订阅

https://ac.nowcoder.com/acm/contest/882/J

当时开局ac率100%,然后就死磕这道题(主要是因为题目短,不想去读别的题),一发RE,一发MLE,然后放弃了……

WJYTXDY

当时的想法很朴素,就是对于每一个位置的前缀和,看在他左边比他小的前缀和的数量(很明显时间复杂度不行,k*1e9,当时抱着如果评测机跑的快的话,说不定……),因为有负数,所以加个基准hash一下,但是题目只说了最大值不超过1e7但是没说最小值啊,然后RE了。然后又改成用unordered_map,就MLE,还没到TLE的测试点,(╥╯^╰╥)

因为注意到前缀和的变化是连续的,就开了两个unordered_map,dp和num,dp[i]表示小于等于i的数的数目,num[i]表示i现在出现的次数,每次ans+=dp[sum-1];   num[sum]++;   dp[sum]=num[sum]+dp[sum-1];//我觉得应该没错

而大佬看到前缀和的变化是连续的之后,就放弃树状数组维护,改用数组+lazy标记,tql

我遇到的最大问题就是区间太大,没有进行有效区间提取(有些全1区间之间不可能对结果产生贡献),如何处理见代码

要学习的地方我觉得是2点:

1.使用dp(贪心)的思想求出每段所在的连续段
2.因为前缀和是连续变化的,可以用lazy标记来代替树状数组来维护。

https://www.cnblogs.com/Yinku/p/11221494.html

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=3e7+5;
//1的个数最多1e7,所以有效的区间最大也就左扩1e7,右扩1e7,所以开3e7 
const int N=1e6+5;
const int R=1e9;
const int base=1e7;
int l[N],r[N],f[N],g[N];
//f[i]以i段右端点为结尾的最大后缀和
//g[i]以i段左端点为开头的最大前缀和 
int sum[M],mp[M],lazy[M];
int main(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d%d",&l[i],&r[i]);
	}
	f[0]=r[0]-l[0]+1;
	for(int i=1;i<n;i++){
		f[i]=max(0,f[i-1]-(l[i]-r[i-1]-1))+r[i]-l[i]+1;
		//0:以i-1段右端点为结尾的最大后缀和不足以跨过[i-1,i]之间的-1
		//f[i-1]-(l[i]-r[i-1]-1):跨过之后还剩下多少贡献给这段
	}
	g[n-1]=r[n-1]-l[n-1]+1; 
	for(int i=n-2;i>=0;i--){
		g[i]=max(0,g[i+1]-(l[i+1]-r[i]-1))+r[i]-l[i]+1;
	}
	int i=0;
	ll ans=0;
	while(i<n){
		int j=i+1;
		while(j<n&&f[j-1]+g[j]>=l[j]-r[j-1]-1){
			//说明这个[j-1,j]之间的-1段可以因为两侧的f[j-1]和g[j]足够大而连接起来
			j++;
		}
		j--;
		//此时j是从i开始最远能够连接到的区间
		int left=max(0,l[i]-g[i]),right=min(R-1,r[j]+f[j]);
		//以l[i]为开头的最大前缀和为g[i],那么我们就可以向左扩g[i](再扩区间和只能是负数了)
		int t=i,mi=R,mx=0;
		sum[0]=0;
		for(int k=left;k<=right;k++){
			//统计这一整段可连接区间的前缀和
			//区间下标从1开始 
			if(k>=l[t]&&k<=r[t]) sum[k-left+1]=sum[k-left]+1;
			else sum[k-left+1]=sum[k-left]-1;
			if(k==r[t]) t++;
			mi=min(mi,sum[k-left+1]+base);//前缀和的最小值 
			mx=max(mx,sum[k-left+1]+base);//前缀和的最大值 
			mp[sum[k-left+1]+base]++;
			//mp记录前缀和出现过的次数 
		} 
		for(int k=mx-1;k>=mi;k--){
			mp[k]+=mp[k+1];
			//mp记录前缀和出现过的次数的后缀和
			//即大于等于k的前缀和出现过的次数 
		} 
		ans+=mp[1+base];//包含最左侧点的贡献
		//最左侧点是那个没有出现的0,mp[1+base]是前缀和大于等于1的数目 
		for(int k=left;k<=right;k++) {
			t=sum[k-left+1]+base;
			//t表示k位置sum的值 
			//mp[t+1]表示比t大的值的个数
			//lazy[c+1]是在k位置左侧的比t大的值的个数的lazy
			mp[t+1]-=lazy[t+1];//处理lazy 
			lazy[t]+=lazy[t+1]+1;//lazy下移 
			lazy[t+1]=0;//清空lazy 
			ans+=mp[t+1]; 
		}
		for(int k=mi;k<=mx;k++){
			mp[k]=0;
			lazy[k]=0;
		}
		i=j+1;
	}   
	printf("%lld\n",ans);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值