CF961E Fufuruma(分块+树状数组)

题意:

找出一个序列里,i<j同时a[i]>j and a[j]>i的对数

题解:

问题可以转化为对每个i,找到下标区间在i+1到a[i]内的j,同时a[j]>=i的数的数量,并求和。

考虑分块,然后对每个块维护一个树状数组。分块做的还是不熟练,比赛的时候数组开小了导致没过。

分块还是很强,可以做一些线段树做不了的东西。

这题的正解是可持久化线段树,明天补。

//一对数成立的标准是
//i<j
//a[i]>=j 
//a[j]>=i
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+100;
typedef long long ll;
int lowbit (int x) {
    return x&-x;
}
int n;
int a[maxn];
int mk[205][maxn];//分块 
int st[maxn];
int ed[maxn];
int pos[maxn];
int num[maxn];
 
void up (int p,int x) {
    //修改第i块的位置
    for (int i=x;i<maxn;i+=lowbit(i)) mk[p][i]++; 
}
int getsum (int p,int x) {
    int ans=0;
    for (int i=x;i;i-=lowbit(i)) ans+=mk[p][i];
    return ans;
}
int main () {
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",a+i),a[i]=min(n,a[i]);
    
    ll ans=0;
    int tt=n/1000;
    for (int i=1;i<=tt;i++) {
        st[i]=(i-1)*1000+1;
        ed[i]=i*1000;
    }
    if (ed[tt]<n) {
        tt++;
        st[tt]=ed[tt-1]+1;
        ed[tt]=n;
    }
    for (int i=1;i<=tt;i++) {
        for (int j=st[i];j<=ed[i];j++) {
            pos[j]=i;
        } 
    } 
    for (int i=1;i<=n;i++) {
        up(pos[i],a[i]);
    }
    for (int i=1;i<=n;i++) {
        //找小于等于a[i]的下标同时a[j]大于等于i的
        //for (int j=i+1;j<=a[i];j++) {
        //    if (a[j]>=i) ans++;
        //}
        int p1=pos[i+1];
        int p2=pos[a[i]];
        if (a[i]<i+1) continue;
        if (p1==p2) {
            for (int j=i+1;j<=a[i];j++) {
                if (a[j]>=i) ans++;
            }
            continue;
        }
        for (int j=p1;j<=p2;j++) {
            ans+=getsum(j,maxn)-getsum(j,i-1);
        }
        for (int j=a[i]+1;j<=ed[p2];j++) if (a[j]>=i) ans--;
        for (int j=st[p1];j<i+1;j++) if (a[j]>=i) ans--; 
    }
    printf("%lld
",ans);
}
原文地址:https://www.cnblogs.com/zhanglichen/p/13599193.html