1. 程式人生 > 其它 >[解題記錄] CF1042D Petya and Array

[解題記錄] CF1042D Petya and Array

CF1042D Petya and Array

題意簡述

有一個長度為 \(n\) 的序列 \(a\),和一個數 \(t\),求有多少個區間 \([l,r]\) 滿足 \(a_l+a_{l+1}+...+a_{r} <t\)\(l\le r\)

\(1≤n≤200000\),\(∣t∣≤2⋅10^{14}\)


解題思路

一道字首和+權值線段樹的好題。

題目要求我們找一個區間 \([l,r]\) ,由於範圍的限制,肯定不能直接列舉,那麼對於列舉區間上的和,我們就應想到去使用字首和,則滿足

的條件便轉化為:

\[\sum_{i=l}^r a_i<t \]\[\sum_{i=1}^r a_i- \sum_{i=1}^{l-1} a_i<t \]\[\sum_{i=1}^r a_i<t+ \sum_{i=1}^{l-1} a_i \]

那麼就可以去列舉 \(l\in[1,n]\)

,找到滿足條件的 \(r\) 即可(注意要倒序列舉)

這個式子其實也可以理解為固定左端點找滿足條件的右端點

利用權值線段樹維護即可,離散化太麻煩,可以使用動態開點的權值線段樹

要注意的一點是,因為沒使用離散化,陣列的下標又不能存負數,所以需要給維護的值賦上一個很大的初值


\(Code\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}
const int N=4e5+10,MAXN=1e15;
int sum[N<<2],n,m,a[N],ls[N<<2],t,rs[N<<2],ans,tot,rt;
void pushup(int p){
    sum[p]=sum[ls[p]]+sum[rs[p]];
}
void add(int &k){
    k=++tot;
}
void upd(int &k,int l,int r,int x){
    if(!k) add(k);
    if(l==r){
	sum[k]++;
	return;
    }
    int mid=(l+r)>>1;
    if(x<=mid) upd(ls[k],l,mid,x);
    else upd(rs[k],mid+1,r,x);
    pushup(k);
}
int query(int p,int l,int r,int x,int y){
    if(x<=l&&y>=r){
	return sum[p];
    }
    int res=0,mid=(l+r)>>1;
    if(x<=mid) res+=query(ls[p],l,mid,x,y);
    if(y>mid) res+=query(rs[p],mid+1,r,x,y);
    return res;
}
signed main(){
    n=read();t=read();
    for(int i=1;i<=n;++i) a[i]=read(),a[i]+=a[i-1];
    for(int i=n;i;--i){
	upd(rt,1,MAXN<<1,a[i]+MAXN);
	ans+=query(rt,1,MAXN<<1,1,a[i-1]+MAXN+t-1);//-1是y因為<
    }
    printf("%lld\n",ans);
    return 0;
}