「2019牛客多校第二场 J」subarray【dp+差分思想】

3 篇文章 0 订阅
题目链接:https://ac.nowcoder.com/acm/contest/882/J

题意

  • 有一个长度为 1 0 9 10^9 109的只包含 1 1 1 − 1 -1 1的数组,然后给你 n ( n &lt; = 1 0 6 ) n(n&lt;=10^6) n(n<=106)个区间 [ l i , r i ] [l_i,r_i] [li,ri],表示只有这 n n n个区间内的所有数为 1 1 1,其余的位置的数都为 − 1 -1 1,求下面这个式子
    ∑ l = 0 1 0 9 − 1 ∑ r = l + 1 1 0 9 − 1 s u m l r &gt; 0 \sum_{l=0}^{10^9-1}\sum_{r=l+1}^{10^9-1}{sum_l^r&gt;0} l=01091r=l+11091sumlr>0
    其中 s u m l r sum_l^r sumlr表示区间和,并且有如下条件
    ∑ i = 1 n ( r i − l i + 1 ) ≤ 1 0 7 \sum_{i=1}^{n}{(r_i-l_i+1)}\leq10^7 i=1n(rili+1)107

题解

  • 首先可以证明所有区间和大于0的区间的并集的长度和为 O ( 1 0 7 ) O(10^7) O(107)的,也就是说,对于每一个可能对答案构成贡献的区间,它的右端点对应的所有可能的左端点的个数为 O ( 1 0 7 ) O(10^7) O(107)的,所以考虑先算出所有的可能被包含在答案区间内的点,然后以所有可能的点为右端点算出可能的左端点,加到 a n s ans ans
  • 首先怎么算出所有可能构成贡献的点呢?由于数组长度太大,而数据中的所有区间的值都为 1 1 1,所以考虑算出数据中的每个区间能拓展到的最左边点和最右边点,然后所有这些算出来的的区间的并集就是所有能构成贡献的点,这些点的数量之和为 O ( 1 0 7 ) O(10^7) O(107)
  • 然后考虑怎么对于这些点算出答案?这里要用到差分思想,从左至右扫的过程中,记录每一个前缀和的数量,那么对于所有前面的前缀和,小于下一个位置的前缀和的数量就能加到 a n s ans ans中,具体操作看代码吧

代码

#include<bits/stdc++.h>

using namespace std;

const int maxn=1e6+10;
const int maxl=1e7+5;
int n,l[maxn],r[maxn],le[maxn],ri[maxn],cur[2*maxl+10];//le[i]表示区间i能拓展到的最左位置,ri[i]为最右位置

int main()
{
   // freopen("/Users/wzw/Desktop/ACM/1.in","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d %d",&l[i],&r[i]);
 
    l[0]=r[0]=-1;l[n+1]=r[n+1]=1e9;
    int sum=0;
    for(int i=1;i<=n;i++) {
        sum+=r[i]-l[i]+1; 
        if(sum>=l[i+1]-r[i]-1) sum-=(l[i+1]-r[i]-1),ri[i]=l[i+1]-1; //还能
        else ri[i]=r[i]+sum,sum=0; //不能继续拓展
    }
    sum=0;
    for(int i=n;i>=1;i--) {
        sum+=r[i]-l[i]+1;
        if(sum>=l[i]-r[i-1]-1) sum-=(l[i]-r[i-1]-1),le[i]=l[i-1]+1;
        else le[i]=l[i]-sum,sum=0;
    }

    int minn=maxl,maxx=maxl,pos=0,now=-1;long long ans=0;  //sum记录当前小于pos-maxl的前缀数量
    for(int i=1;i<=n;i++) {                  //pos-maxl表示当前的前缀和
        if(now<le[i]) {
            for(int j=minn;j<=maxx;j++) cur[j]=0;
            minn=maxx=maxl;pos=maxl;
            sum=0;now=le[i];cur[maxl]=1;
        }

        while(now<l[i]) {
            sum-=cur[--pos];
            cur[pos]++;
            ans+=sum;
            now++;
            minn=min(minn,pos);
        }

        while(now<=r[i]) {
            sum+=cur[pos++];
            cur[pos]++;
            ans+=sum;
            now++;
            maxx=max(maxx,pos);
        }

        while(now<=ri[i]) {
            sum-=cur[--pos];
            cur[pos]++;
            ans+=sum;
            now++;
            minn=min(minn,pos);
        }
    }
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值