P5677 [GZOI2017]配对统计
提供两种与众不同的做法。
题意
若在整个数列中距离 最近的数包含 ,则称 是好的配对(为了方便叙述,本文中认为 配对了 , 被 配对了)。多组询问,每次询问区间 中有多少组好的配对。
分析
首先可以注意到,若 是好的配对,则 不一定是,手动模拟即可明白。
其次题目中保证 时,,所以猜测正解与值域有关,但是 这个范围令人十分不爽,于是我们考虑把它离散化掉: 且当 时,。
这个时候可以发现一个有意思的性质:对于 ,能被他配对的 只能为 或 或二者均可。
又因为没有修改操作,于是我们可以预处理:
- 数组,。
- 数组, 表示小于 且能被 配对的数 ,没有则为 。
- 数组, 表示小于 且能配对 的数 ,没有则为 。
- 数组, 表示大于 且能被 配对的数 ,没有则为 。
- 数组, 表示大于 且能配对 的数 ,没有则为 。
for(int i=1;i<=n;i++){//b数组是离散化前的a数组
if(a[i]==n||(a[i]>1&&b[a[i]]-b[a[i]-1]<=b[a[i]+1]-b[a[i]])){
fl[a[i]]=a[i]-1;
ffr[a[i]-1]=a[i];
}
if(a[i]==1||(a[i]<n&&b[a[i]]-b[a[i]-1]>=b[a[i]+1]-b[a[i]])){
fr[a[i]]=a[i]+1;
ffl[a[i]+1]=a[i];
}
}
然后开一个数组 表示 是否在当前状态下存在,接下来就可以很快地进行插入和删除一个数了。
- 若要插入一个数 ,只需要判断可能与其配对的数 是否存在即可,把存在的数量加入到答案里。
- 若要删除一个数 ,只需要判断可能与其配对的数 是否存在即可,把存在的数量从答案里减去。
这为接下来两种做法的实现奠定了基础。
莫队
既然我们掌握了快速插入和删除一个数的方法,那么只需要把询问离线,再套莫队板子即可。
然而最后一个点 TLE 了,要加上玄学的奇偶性排序优化才可以过。
树状数组
莫队这么压线的做法肯定不是正解。
于是考虑从题目本身出发。
首先,看到求区间某种东西的个数时,我总是立刻有一个平凡而大胆的想法:
“既然要求 ,那么先求出前缀和 和 再相减不就行了么!”
想法很美好,然而现实很残酷。
因为这样做漏减了左右值分别在 和 的好的配对,于是开始思考:怎样求出它呢?
其实,我们可以把一个好的配对 认为是一个区间 (假设是 的情况),那么上面要求的那个值就相当于求所有包含 的区间 的个数(假定当前只加入了 ),也就是使 不是区间右端点的包含 的区间的个数,这样保证了这些区间都横跨 和 。
这样我们只要写某种数据结构存下当前状态左右值分别在 和 的好的配对,每次我们加入一个配对 时,只要把 的每个数增加 即可,不难想到运用包含差分思想的树状数组来解决这个问题。
代码
莫队
#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
long long x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int N=4e5+10;
int n,m,len;
ll a[N],b[N],cnt,ans;
int fl[N],fr[N],f[N],ffl[N],ffr[N];
struct asdf{
int l,r,id;
}d[N];
bool cmp(asdf x,asdf y){
return (x.l/len<y.l/len||(x.l/len==y.l/len&&((x.l/len)&1?x.r<y.r:x.r>y.r)));
}
void add(int x){
f[x]=1;
cnt+=f[fl[x]]+f[fr[x]]+f[ffl[x]]+f[ffr[x]];
}
void del(int x){
f[x]=0;
cnt-=f[fl[x]]+f[fr[x]]+f[ffl[x]]+f[ffr[x]];
}
int main(){
n=read();m=read();
len=sqrt(m);
for(int i=1;i<=n;i++)
b[i]=a[i]=read();
sort(b+1,b+n+1);
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+n+1,a[i])-b;
for(int i=1;i<=n;i++){
if(a[i]==n||(a[i]>1&&b[a[i]]-b[a[i]-1]<=b[a[i]+1]-b[a[i]])){
fl[a[i]]=a[i]-1;
ffr[a[i]-1]=a[i];
}
if(a[i]==1||(a[i]<n&&b[a[i]]-b[a[i]-1]>=b[a[i]+1]-b[a[i]])){
fr[a[i]]=a[i]+1;
ffl[a[i]+1]=a[i];
}
}
for(int i=1;i<=m;i++){
d[i].l=read();
d[i].r=read();
d[i].id=i;
}
sort(d+1,d+m+1,cmp);
int ql=1,qr=0;
for(int i=1;i<=m;i++){
while(ql>d[i].l) add(a[--ql]);
while(qr<d[i].r) add(a[++qr]);
while(ql<d[i].l) del(a[ql++]);
while(qr>d[i].r) del(a[qr--]);
ans+=cnt*d[i].id;
}
cout<<ans<<endl;
return 0;
}
树状数组
#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
long long x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int N=4e5+10;
int n,m;
ll a[N],b[N],cnt,ans,sum[N],c[N],e[N];
int fl[N],fr[N],f[N],ffl[N],ffr[N];
struct asdf{
int l,r,id;
}d[N];
bool cmp(asdf x,asdf y){
return x.r<y.r;
}
void Add(int x,int v){
for(;x<=n;x+=x&-x)
c[x]+=v;
}
ll ask(int x){
ll ans=0;
for(;x;x-=x&-x)
ans+=c[x];
return ans;
}
void add(int x){
f[a[x]]=1;
if(f[fl[a[x]]]){
sum[x]++;
Add(e[fl[a[x]]],1);
Add(x,-1);
}
if(f[fr[a[x]]]){
sum[x]++;
Add(e[fr[a[x]]],1);
Add(x,-1);
}
if(f[ffl[a[x]]]){
sum[x]++;
Add(e[ffl[a[x]]],1);
Add(x,-1);
}
if(f[ffr[a[x]]]){
sum[x]++;
Add(e[ffr[a[x]]],1);
Add(x,-1);
}
}
int main(){
n=read();m=read();
for(int i=1;i<=n;i++)
b[i]=a[i]=read();
sort(b+1,b+n+1);
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+n+1,a[i])-b;
e[a[i]]=i;
}
for(int i=1;i<=n;i++){
if(a[i]==n||(a[i]>1&&b[a[i]]-b[a[i]-1]<=b[a[i]+1]-b[a[i]])){
fl[a[i]]=a[i]-1;
ffr[a[i]-1]=a[i];
}
if(a[i]==1||(a[i]<n&&b[a[i]]-b[a[i]-1]>=b[a[i]+1]-b[a[i]])){
fr[a[i]]=a[i]+1;
ffl[a[i]+1]=a[i];
}
}
for(int i=1;i<=m;i++){
d[i].l=read();
d[i].r=read();
d[i].id=i;
}
sort(d+1,d+m+1,cmp);
int q=0;
for(int i=1;i<=m;i++){
while(q<d[i].r){
sum[q+1]=sum[q];
q++;
add(q);
}
ans+=(sum[d[i].r]-sum[d[i].l-1]-ask(d[i].l-1))*d[i].id;
}
cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号