Codeforces1042D——线段树|树状数组|分治+尺取

题目链接:https://vjudge.net/contest/357503#problem/D

题目大意:

给你一个由n个整数组成的数组a,请你计算二元组(l,r)的个数,其中(l,r)满足条件

a_{l}+a_{l+1}.....a_{r}<t

Input

第一行两个整数n和t (1 ≤ n ≤ 200,000 , |t| ≤ 2e14 )

第二行为一个序列: a1,a2...an ,注意可能有负数和零

Output

二元组个数

题解:

解法一:树状数组|线段树

首先我们可以想想暴力的解法,先预处理出前缀和,然后求出枚举l,r,求出符合条件的二元组个数(即满足sum[j]-sum[i-1]<t)

然后我们考虑可以怎么优化,首先我们肯定要枚举l和r中的一个,假如我们用i来枚举r,那么我们需要求出[1,i]的前缀和中满足sum[i-1]>sum[j]-t的个数,转换一下求是求出[1,i]区间里面,sum值大于sum[j]-t的个数,这样我们就把问题转换成了一个很经典的树状数组或者线段树求[1,i]区间里面小于某个定值的个数。

因为数据范围比较大,所以我们需要先离散化,然后在维护一个数据结构求答案。

代码实现(树状数组):

#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#define PI atan(1.0)*4
#define E 2.718281828
#define rp(i,s,t) for (register int i = (s); i <= (t); i++)
#define RP(i,t,s) for (register int i = (t); i >= (s); i--)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define debug printf("ac\n");
using namespace std;
inline int read()
{
    int a=0,b=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            b=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        a=(a<<3)+(a<<1)+c-'0';
        c=getchar();
    }
    return a*b;
}
const int INF = 0x3f3f3f3f;
const int N = 2e5+7;
ll sum[N],a[N],b[N];
ll n,t;
inline lowbit(int x){return x&-x;}
inline void update(int pos,int val){
    for(int i=pos;i<=n;i+=lowbit(i)) b[i]+=val;
}
inline ll query(int pos){
    ll ans=0;
    for(int i=pos;i>=1;i-=lowbit(i)) ans+=b[i];
    return ans;
}
vector<ll> v;
int main(){
    cin>>n>>t;
    ll res=0;
    rp(i,1,n){
        cin>>a[i];
        sum[i]=sum[i-1]+a[i];
        v.pb(sum[i]);
        if(sum[i]<t) res++;
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    rp(i,1,n){
        int x=lower_bound(v.begin(),v.end(),sum[i])-v.begin()+1;
        int y=lower_bound(v.begin(),v.end(),sum[i]-t+1)-v.begin();
        // cout<<x<<" "<<y<<endl;
        res+=(i-1-query(y));
        update(x,1);
    }
    cout<<res<<endl;
    return 0;
}

解法二:

分治+尺取法,应该算是这个问题的标准解法吧,毕竟还挺精妙的。

首先类似于归并排序的写法,把序列分成两半,然后分别进行排序,对于每一半里面的答案可以递归统计,因此我们只需要统计出跨过中间点的贡献,这时可以用尺取法处理求出答案。

至于为什么能尺取法?

因为排完序后两半的前缀和都是递增的,那么我们就可以从最左边开始枚举l,从中间开始枚举r,算出答案。

这里用到了inplace_merge,就是把两个有序递增的序列合并成了一个有序递增的序列

代码实现:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5+7;
ll n,t;
ll a[N];
ll ans=0;
void merge(int l,int r){
    if(l==r) return ;
    int m=l+r>>1;
    int i=l,j=m+1;
    merge(l,m);merge(m+1,r);
    for(;i<=m;i++,ans+=j-m-1)
        for(;j<=r&&a[j]<t+a[i];) j++;
    inplace_merge(a+l,a+m+1,a+r+1);
}
int main(){
    cin>>n>>t;
    for(int i=1;i<=n;i++) cin>>a[i],a[i]+=a[i-1];
    merge(0,n);
    cout<<ans<<endl;
    return 0;
}
发布了342 篇原创文章 · 获赞 220 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43472263/article/details/104432968
今日推荐