2019牛客多校训练营第七场E——Find the median——离散+树状数组+二分(树状数组是个大宝贝)

原题题址
首先声明,本篇博客不介绍离散化,二分寻找
想看线段树解法的戳这里
我绝不是故意把一题写两篇博客,恶意提高阅读量啊。首先我是觉得这两种解法的数学要求都不高,但是想法不同(好吧其实大部分都是相同的),其实主要原因就是我开始没想到我会再用树状数组在写一遍,我开始了解到的方法有点恐怖。有一说一,我不想看了都,因为就是一个可以线段树无脑(没有任何特殊的数学想法,除了题目数据的原因必须的处理以外,其他就是正常的处理写的题目,为啥还要掏空心思去搞树状数组)。后来我就发现一篇博客介绍了一个十分巧妙的方法(没线段树快),我就想着这得记录一下啊。就有了这篇博客。
不得不提的是区间都是左闭右开,为了防止离散后的区间冲突。
首先给代码再分析代码的关键操作。
代码:

#include
using namespace std;
typedef long long ll;
const int maxn = 8e5+5;
const int INF = 1e9;
int N;
int X1,X2,Y1,Y2,A1,A2,B1,B2,C1,C2,M1,M2;
int L[maxn],R[maxn],disc[maxn<<1];
ll b1[maxn],b2[maxn],cnt = 0;
int lowbit(int x){
    return x&-x;
}

void Updata(ll a[],int pos,int value){
    while(pos <= cnt){
        a[pos] += value;
        pos += lowbit(pos);
    }
}
ll Query(ll a[],int x){
    ll ans = 0;
    while(x){
        ans += a[x];
        x -= lowbit(x);
    }
    return ans;
}
int main(){
	 scanf("%d",&N);
	 scanf("%d%d%d%d%d%d",&X1,&X2,&A1,&B1,&C1,&M1);
	 scanf("%d%d%d%d%d%d",&Y1,&Y2,&A2,&B2,&C2,&M2);
	 L[1] = min(X1,Y1) + 1;
	 R[1] = max(X1,Y1) + 1;
	 L[2] = min(X2,Y2) + 1;
	 R[2] = max(X2,Y2) + 1;
	 ll x,y;
	 for(int i = 3;i <= N;i++){
		x = ((1ll*A1 * X2 % M1 + 1ll * B1 * X1 %M1)%M1 + C1)%M1;
		y = ((1ll*A2 * Y2 % M2 + 1ll * B2 * Y1 %M2)%M2 + C2)%M2;
		X1 = X2;X2 = x;
		Y1 = Y2;Y2 = y;
		L[i] = min(x,y) + 1;
		R[i] = max(x,y) + 1;
	}
		for(int i = 1;i <= N;i++){
		disc[++cnt] = L[i];
		disc[++cnt] = R[i] + 1;
	//	printf("%d %d\n",L[i],R[i]);
	}
	memset(b1,0,sizeof(b1));
	memset(b2,0,sizeof(b2));
	sort(disc + 1,disc + 1 + cnt);
	 cnt = unique(disc + 1,disc + 1 + cnt) - disc - 1;
	ll All = 0;
	for(int i = 1;i <= N;i++){
		int l,r;
		All += R[i] - L[i] + 1;
		l = lower_bound(disc + 1,disc + cnt + 1,L[i]) - disc;
		r = lower_bound(disc + 1,disc + cnt + 1,R[i] + 1) - disc; 
        Updata(b1,l,-L[i]);
        Updata(b1,r,R[i]+1);
        Updata(b2,l,1);
        Updata(b2,r,-1);
        int left = 1,right = INF;
        ll pd = (All + 1)/2;
        while(left < right){
            int mid = (left + right)>>1;
            int pos = upper_bound(disc + 1,disc + cnt + 1,mid) - disc - 1;
            ll tmp = Query(b1,pos) + Query(b2,pos)*(mid + 1);
            if(tmp < pd) left = mid + 1;
            else right = mid;
        }
        printf("%d\n",left);
	}
	
	return 0;
}

关键操作:

        Updata(b1,l,-L[i]);
        Updata(b1,r,R[i]+1);
        Updata(b2,l,1);
        Updata(b2,r,-1);
        &
        ll tmp = Query(b1,pos) + Query(b2,pos)*(mid + 1);

关键操作就是这两个树状数组的更新&区间和的表达式
我们可以看到前两更新(第一个树状数组的更新)
U p d a t a ( b 1 , l , − L [ i ] ) Updata(b1,l,-L[i]) Updata(b1,l,L[i]) U p d a t a ( b 1 , r , R [ i ] + 1 ) Updata(b1,r,R[i]+1) Updata(b1,r,R[i]+1)
我们知道树状数组求区间的和。
我们可以知道,完整的区间如果都在当前区间中,那么这更新的两点对区间和的贡献就是 R [ i ] − L [ i ] + 1 R[i] - L[i] + 1 R[i]L[i]+1,perfect,这正是我们想要的。代码可以用 Q u e r y ( b 1 , p o s ) Query(b1,pos) Query(b1,pos)表示

如果完整区间不在其中,那么必然是少了右端点。
U p d a t a ( b 2 , l , 1 ) Updata(b2,l,1) Updata(b2,l,1) U p d a t a ( b 2 , r , − 1 ) Updata(b2,r,-1) Updata(b2,r,1)
就完美解决了端点缺失的问题。
我们也可以从代码中看出如果区间完整,b2的区间和就是0。反之,区间和就是代表有几个没有右端点的区间,很显然要加上右端点,来使实际上的区间和完整,我们要加上的右端点就是我们当前查询的区间的右端点。此时 Q u e r y ( b 1 , p o s ) + Q u e r y ( b 2 , p o s ) ∗ ( m i d + 1 ) Query(b1,pos) + Query(b2,pos)*(mid + 1) Query(b1,pos)+Query(b2,pos)(mid+1)就很好的解决了这个问题
同时,他也和前面的问题完美契合。

此时,一切都那么合理,自然。

你可能感兴趣的:(2019牛客多校训练营第七场E——Find the median——离散+树状数组+二分(树状数组是个大宝贝))