牛客多校第二场j题

给你一段含1和-1的序列,序列长度固定为 1 0 9 10^9 ,现在给你n个区间,区间内全为1,然后问你有多少个区间和大于0;
其中 l i < r i l_i<r_i
r i + 1 < l i + 1 r_i+1<l_{i+1} for 0 i < n 1 \leq i < n - 1
0 l 0 0\leq l0
l i r i l_i\leq r_i
r n 1 < 1 0 9 r_{n-1}<10^9
( l i r i + 1 ) 1 0 7 \sum (l_i-r_i+1)\leq10^7
这题看了大佬的题解之后还是疯狂wa,调试了一个下午,补题之路非常漫长
因为 ( l i r i + 1 ) 1 0 7 \sum (l_i-r_i+1)\leq10^7 可知1的个数最多只有 1 0 7 10^7 ,也就是说有绝大部分的-1在无效区域,dp好像不行,因为两段可能连起来为有效区域,你每一段1必须考虑后面所有的1段和前面所有的1段是不是可以连接起来,但考虑所有的1段实在是太耗时,那么有没有办法给缩小一下范围呢?
显然可以,我们想办法把整一段序列分成相互隔绝的几段独立序列,然后分别考虑,代码详解:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e7+20;
typedef long long ll;
int l[maxn/10],r[maxn/10];
int rlt[maxn/10],lrt[maxn/10],sum[maxn*3],num[maxn*3],lnum[maxn*3];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&l[i],&r[i]);
    }
    //l,r记录每段1段的左端点和右端点
   rlt[1]=r[1]-l[1]+1;
   //rlt[i]记录从第i段的右端点往左的最大前缀和,显然第一段就是第一段1的长度
	for(int i=2;i<=n;i++)
	{
		rlt[i]=r[i]-l[i]+1;
		if(rlt[i-1]>(l[i]-r[i-1]-1))
		rlt[i]+=rlt[i-1]-(l[i]-r[i-1]-1);
	}
	//如果rlt[i-1]大于第i段和第i-1段1之间的-1,则rlt[i-1]对rlt[i]常生了贡献
	lrt[n]=r[n]-l[n]+1;
	//lrt[i]记录从第i段左端点往右的最大前缀和,显然第n段就是第n段1的长度
	for(int i=n-1;i>=1;i--)
	{
		lrt[i]=r[i]-l[i]+1;
		if(lrt[i+1]-(l[i+1]-r[i]-1)>0)
		lrt[i]+=lrt[i+1]-(l[i+1]-r[i]-1);
	}
	//同理
    int x=1;
    ll ans=0;//记录结果
    int bas=1e7;
    //
    while(x<=n)
    {
        int i=x;
        int j=i+1;
        while(j<=n&&lrt[j]+rlt[j-1]>=l[j]-r[j-1]-1)
        {
            j++;
        }
        //这里便是来分割独立序列了,因为如果从第j段左端点往右的最大前缀和加上从第j-1段右端点往左的最大前缀和都不足以抵消第j段和第j-1段之间的-1,这第j段必然无法去前面连接起来;
        j--;
        int end=1e9-1;
        int mx=0,mi=2e9;
        int begins=max(0,l[i]-lrt[i]);
        int ends=min(end,rlt[j]+r[j]);
        sum[0]=0;//记录独立序列段的前缀和;
        for(int k=begins;k<=ends;k++)
        {
            int a=k-begins+1;
            sum[a]=sum[a-1];
            if(k<l[i]||k>r[i])
            sum[a]--;
            else sum[a]++;
            if(k==r[i])
            i++;
            mx=max(mx,sum[a]+bas);//加上bas的好处是防止有负数
            mi=min(mi,sum[a]+bas);
            num[sum[a]+bas]++;//记录前缀和为sum[a]+bas的数量
        }
        for(int k=mx-1;k>=mi;k--)
        {
            num[k]+=num[k+1];//num[k]记录大于k-1的前缀和有多少个点
        }
        ans+=num[bas+1];//跟后面区间无关的要加上
        for(int k=begins;k<=ends;k++)
        {
            int a=sum[k-begins+1]+bas;//a为k位置的前缀和加上bas;
            num[a+1]-=lnum[a+1];
            //num[a+1]为大于a的前缀和的个数,lnum[a+1]为k位置往左大于a的前缀和的个数,两者相减即得k往左右多少个前缀和大于a,那每个大于a的前缀和减a必然大于零,即从k为置到前缀和大于a的区间满足条件
            lnum[a]+=lnum[a+1]+1;
            lnum[a+1]=0;//因为num[a+1]在k位置已经减掉在k位置的lnum[k+1]的值了,所以这里直接置零
            ans+=num[a+1];
        }
        for(int k=mi;k<=mx;k++)
            num[k]=0,lnum[k]=0;
        x=j+1;
    }
    cout<<ans<<endl;
}在这里插入代码片
发布了109 篇原创文章 · 获赞 35 · 访问量 6014

猜你喜欢

转载自blog.csdn.net/weixin_43965698/article/details/97394108