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