Ynoi2019模拟赛题解

Y n o i 2019 Ynoi2019 Ynoi2019模拟赛题解

前言:第一次做 Y n o i Ynoi Ynoi还是有被离谱到的,我从来没有做到过这么卡常的题目,我到现在 T 1 T1 T1都还是 70 70 70分, l x l lxl lxl毒瘤名不虚传啊。但是不得不说, Y n o i Ynoi Ynoi的题目质量很高,对提高 D S DS DS水平还是很有帮助的。

T 1 : T1: T1: Y u n o Yuno Yuno l o v e s loves loves s q r t sqrt sqrt t e c h n o l o g y technology technology I I I

给你一个长为 n n n的排列, m m m次询问,每次查询一个区间的逆序对数,强制在线。

1 ≤ n , m ≤ 1 0 5 。 1\leq n,m \leq 10^5。 1n,m105

这题非常卡常,这里我就简述做法。首先考虑序列分块,对于询问 [ l , r ] [l,r] [l,r],计 l l l所在块为 s s s r r r所在块为 t t t,则可以预处理出 [ s + 1 , t − 1 ] [s+1,t-1] [s+1,t1]这些块的逆序对,具体方法就是,先对每个块块内排序,枚举两个块并通过归并排序 O ( n ) O(\sqrt{n}) O(n )计算这两个块互相的贡献,复杂度 O ( n n ) O(n\sqrt{n}) O(nn ),记为 f ( a , b ) f(a,b) f(a,b),再用树状数组 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)求出每个块内的逆序对数,即为 f ( a , a ) f(a,a) f(a,a)。然后做一个类似二维前缀和: s ( a , b ) = f ( a , b ) + s ( a , b − 1 ) + s ( a + 1 , b ) − s ( a + 1 , b − 1 ) s(a,b)=f(a,b)+s(a,b-1)+s(a+1,b)-s(a+1,b-1) s(a,b)=f(a,b)+s(a,b1)+s(a+1,b)s(a+1,b1) s ( a , b ) s(a,b) s(a,b)就是块 a a a到块 b b b的逆序对数。然后再算散块和散块之间的贡献,之间块内已经排过序了,之间把 [ l , e d ( s ) ] [l,ed(s)] [l,ed(s)] [ s t ( t ) , r ] [st(t),r] [st(t),r]从它们的块里抽出来,直接归并计算贡献,复杂度 O ( n ) O(\sqrt{n}) O(n )。接下来算散块和 [ s + 1 , t − 1 ] [s+1,t-1] [s+1,t1]互相的贡献,可以对于每个块 i i i预处理 [ 1 , e d ( i ) ] [1,ed(i)] [1,ed(i)]比每个数小的数个数,这个直接用一个空间 O ( n n ) O(n\sqrt{n}) O(nn )的前缀和计算即可,然后 O ( 1 ) O(1) O(1)计算对于左右散块每个数,块 [ s + 1 , t − 1 ] [s+1,t-1] [s+1,t1]对其的贡献。最后算 [ l , e d ( s ) ] [l,ed(s)] [l,ed(s)] [ s t ( t ) , r ] [st(t),r] [st(t),r]块内逆序对数,要处理两个数组 p r e f [ i ] pref[i] pref[i]表示 i i i所在块的开头到 i i i逆序对数, s u f [ i ] suf[i] suf[i]表示 i i i i i i所在块的结尾逆序对数。然后记得特判 l l l r r r同块的情况,这时逆序对数即为 p r e f [ r ] − p r e f [ l − 1 ] pref[r]-pref[l-1] pref[r]pref[l1]再减去 [ s t ( s ) , l − 1 ] [st(s),l-1] [st(s),l1]的逆序对数,后者之间归并排序求即可。复杂度 O ( n n ) O(n\sqrt{n}) O(nn )

代码放一下,但卡不过去,常数有点大。


#include<iostream>
#include<algorithm>
#include<string.h>
#define gc getchar
#define pc putchar
#define pb push_back
#define f first
#define s second
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
template<class Typ> Typ &Rd(Typ &x){
	char ch=gc(),sgn=0; x=0;
	for(;ch<'0'||ch>'9';ch=gc()) sgn|=ch=='-';
	for(;ch>='0'&&ch<='9';ch=gc()) x=x*10+(ch^48);
	return sgn&&(x=-x),x;
}
template<class Typ> void Wt(Typ x){
	if(x<0) pc('-'),x=-x;
	if(x>9) Wt(x/10);
	pc(x%10^48);
}
typedef long long LL;
const int N=1e5+5;
const int S=300;
LL Ans[400][400],ans;
LL Tr[N],cnt[400][N];
LL prf[N],suf[N];
int n,Q,A[N],in[N];
int st[400],ed[400]; pii B[N];
int Ma[400],Mb[400],la,lb;
void Add(int ps,int vl){
    for(;ps<=n;ps+=ps&-ps)
        Tr[ps]+=vl;
}
LL Ask(int ps){
    LL res=0;
    for(;ps;ps-=ps&-ps)
        res+=Tr[ps];
    return res;
}
int main(){
    Rd(n),Rd(Q);
    for(int i=1;i<=n;i++) B[i]={Rd(A[i]),i};
    for(int i=1;i<=n;i++) in[i]=(i-1)/S+1;
    for(int i=1;i<=in[n];i++)
        st[i]=(i-1)*S+1,ed[i]=min(i*S,n);
    for(int i=1;i<=in[n];i++){
        sort(B+st[i],B+ed[i]+1);
        memset(Tr,0,sizeof(Tr)),Add(A[st[i]],1);
        for(int j=st[i]+1;j<=ed[i];j++)
            prf[j]=prf[j-1]+Ask(n)-Ask(A[j]),Add(A[j],1);
        Ans[i][i]=prf[ed[i]];
        memset(Tr,0,sizeof(Tr)),Add(A[ed[i]],1);
        for(int j=ed[i]-1;j>=st[i];j--)
            suf[j]=suf[j+1]+Ask(A[j]),Add(A[j],1);
    }
    for(int i=1;i<=in[n];i++)
        for(int j=i+1;j<=in[n];j++)
            for(int p1=st[i],p2=st[j];p1<=ed[i];p1++){
                while(p2<=ed[j]&&B[p2].f<=B[p1].f) p2++;
                Ans[i][j]+=(LL)(p2-st[j]);
            }
    for(int len=1;len<=in[n];len++)
        for(int i=1;i+len-1<=in[n];i++){
            int j=i+len-1;
            Ans[i][j]+=Ans[i+1][j]+Ans[i][j-1];
            Ans[i][j]-=Ans[i+1][j-1];
        }
    for(int i=1;i<=in[n];i++){
        for(int j=1;j<=n;j++) cnt[i][j]=cnt[i-1][j];
        for(int j=st[i];j<=ed[i];j++) cnt[i][A[j]]++;
    }
    for(int i=1;i<=in[n];i++)
        for(int j=1;j<=n;j++) cnt[i][j]+=cnt[i][j-1];
    for(int i=1;i<=Q;i++){
        LL L,R; Rd(L)^=ans,Rd(R)^=ans;
        if(in[L]==in[R]){
            ans=prf[R]-(L!=st[in[L]])*prf[L-1],la=lb=0;
            for(int j=st[in[L]];j<=ed[in[L]];j++)
                if(B[j].s<L) Ma[++la]=B[j].f;
                else if(B[j].s<=R) Mb[++lb]=B[j].f;
            for(int p1=1,p2=1;p1<=la;p1++){
                while(p2<=lb&&Mb[p2]<=Ma[p1]) p2++;
                ans-=(LL)(p2-1);
            }
            Wt(ans),pc('\n');
        }else{
            ans=Ans[in[L]+1][in[R]-1],la=lb=0;
            ans+=suf[L]+prf[R];
            for(int j=L;j<=ed[in[L]];j++)
                ans+=cnt[in[R]-1][A[j]]-cnt[in[L]][A[j]];
            for(int j=st[in[R]];j<=R;j++){
                ans+=(LL)(ed[in[R]-1]-st[in[L]+1]+1);
                ans-=cnt[in[R]-1][A[j]]-cnt[in[L]][A[j]];
            }
            for(int j=st[in[L]];j<=ed[in[L]];j++)
                if(B[j].s>=L) Ma[++la]=B[j].f;
            for(int j=st[in[R]];j<=ed[in[R]];j++)
                if(B[j].s<=R) Mb[++lb]=B[j].f;
            for(int p1=1,p2=1;p1<=la;p1++){
                while(p2<=lb&&Mb[p2]<=Ma[p1]) p2++;
                ans+=(LL)(p2-1);
            }
            Wt(ans),pc('\n');
        }
    }
    return 0;
}

T 2 : T2: T2: Y u n o Yuno Yuno l o v e s loves loves s q r t sqrt sqrt t e c h n o l o g y technology technology I I II II

给你一个长为 n n n的序列 a a a m m m次询问,每次查询一个区间的逆序对数。

1 ≤ n , m ≤ 1 0 5 , 0 ≤ a i ≤ 1 0 9 。 1\leq n,m\leq 10^5,0\leq a_i\leq 10^9。 1n,m1050ai109

注意本题时限 250 m s 250ms 250ms T 1 T1 T1时限 750 m s 750ms 750ms都极为卡常,显然 T 1 T1 T1做法常数太大,不能通过此题。注意到可以离线,我们需要常数更小的离线做法。注意到莫队可以做到 O ( n m log ⁡ n ) O(n\sqrt m\log{n}) O(nm logn),但无法通过此题。这时候就要引入黑科技 莫队二次离线 \textbf{莫队二次离线} 莫队二次离线。注意到莫队每次向右移动,增加的贡献是 [ l , r ] [l,r] [l,r]中比 r r r大的数,计 f ( a , b ) f(a,b) f(a,b)表示 [ 1 , b ] [1,b] [1,b]里比 a a a位置的数大的数个数,则贡献可表示为 f ( r , r ) − f ( r , l − 1 ) f(r,r)-f(r,l-1) f(r,r)f(r,l1),对于每次右端点移动, f ( r , r ) f(r,r) f(r,r)可以 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)预处理,下面考虑如何求 f ( r , l − 1 ) f(r,l-1) f(r,l1)。注意到可以将所以 f ( r , l − 1 ) f(r,l-1) f(r,l1)再次离线,按第二维排序,并向数据结构中进行 n n n次插入, n m n\sqrt{m} nm 次查询求得所有 f ( r , l − 1 ) f(r,l-1) f(r,l1)。可以使用值域分块做到插入 O ( n ) O(\sqrt{n}) O(n ),查询 O ( 1 ) O(1) O(1),所以总复杂度即为 O ( n n + n m ) O(n\sqrt{n}+n\sqrt{m}) O(nn +nm )。对左端点移动的处理同右端点。下面还有一个问题:空间复杂度 O ( n m ) O(n\sqrt{m}) O(nm ),因为要把所有询问离线,无法满足空间限制。注意到莫队移动左右端点时会先移动完一个再移动另一个,那我们只要记录移动开始到移动结束的位置即可,询问时直接从开始位置循环到结束位置,空间复杂度 O ( n + m ) O(n+m) O(n+m)

代码:

#include<iostream>
#include<vector>
#include<string.h>
#include<algorithm>
#define pb push_back
#define f first
#define s second
using namespace std;
typedef long long ll;
typedef double lf;
typedef pair<int,int> pii;
#define gc getchar 
#define pc putchar
template<class Typ> Typ &Rd(Typ &x){
	char ch=gc(),sgn=0; x=0;
	for(;ch<'0'||ch>'9';ch=gc()) sgn|=ch=='-';
	for(;ch>='0'&&ch<='9';ch=gc()) x=x*10+(ch^48);
	return sgn&&(x=-x),x;
}
template<class Typ> void Wt(Typ x){
	if(x<0) pc('-'),x=-x;
	if(x>9) Wt(x/10);
	pc(x%10^48);
}
const int N=1e5+5;
const int M=340;
int n,m,A[N],Num[N],len,Tr[N]; 
struct Qry{int l,r,id;}Qr[N];
struct Opt{int st,ed,sgn,id;};
vector<Opt> Lc[N],Rc[N];
ll s1[N],s2[N],ans[N];
int Tg[M],sum[N],st[M],ed[M];
int in(int x){return (x-1)/M+1;}
bool cmp(Qry a,Qry b){return in(a.l)==in(b.l)?a.r<b.r:in(a.l)<in(b.l);}
void Add(int ps){for(;ps<=len;ps+=ps&-ps) Tr[ps]++;}
int Ask(int ps){int sm=0; for(;ps;ps-=ps&-ps) sm+=Tr[ps]; return sm;}
int main(){
    Rd(n),Rd(m);
    for(int i=1;i<=n;i++) Num[++len]=Rd(A[i]);
    sort(Num+1,Num+len+1),len=unique(Num+1,Num+len+1)-Num-1;
    for(int i=1;i<=n;i++) A[i]=lower_bound(Num+1,Num+len+1,A[i])-Num;
    for(int i=1;i<=n;i++) s1[i]=s1[i-1]+Ask(len)-Ask(A[i]),Add(A[i]);
    memset(Tr,0,sizeof(Tr));
    for(int i=n;i>=1;i--) s2[i]=s2[i+1]+Ask(A[i]-1),Add(A[i]);
    for(int i=1;i<=m;i++) Rd(Qr[i].l),Rd(Qr[i].r),Qr[i].id=i;
    sort(Qr+1,Qr+m+1,cmp);
    for(int i=1,l=1,r=0;i<=m;i++){
        if(r<Qr[i].r) ans[Qr[i].id]+=s1[Qr[i].r]-s1[r],
            Rc[l-1].pb({r+1,Qr[i].r,-1,Qr[i].id}),r=Qr[i].r;
        if(r>Qr[i].r) ans[Qr[i].id]-=s1[r]-s1[Qr[i].r],
            Rc[l-1].pb({Qr[i].r+1,r,1,Qr[i].id}),r=Qr[i].r;
        if(Qr[i].l<l) ans[Qr[i].id]+=s2[Qr[i].l]-s2[l],
            Lc[r+1].pb({l-1,Qr[i].l,-1,Qr[i].id}),l=Qr[i].l;
        if(Qr[i].l>l) ans[Qr[i].id]-=s2[l]-s2[Qr[i].l],
            Lc[r+1].pb({Qr[i].l-1,l,1,Qr[i].id}),l=Qr[i].l;
    }
    for(int i=1;i<=in(len);i++)
        st[i]=(i-1)*M+1,ed[i]=min(i*M,len);
    for(int i=1;i<=n;i++){
        for(int j=A[i]-1;j>=st[in(A[i])];j--) sum[j]++;
        for(int j=in(A[i])-1;j>=1;j--) Tg[j]++;
        for(auto j:Rc[i]) for(int k=j.st;k<=j.ed;k++)
            ans[j.id]+=j.sgn*(sum[A[k]]+Tg[in(A[k])]);
    }
    memset(sum,0,sizeof(sum)),memset(Tg,0,sizeof(Tg));
    for(int i=n;i>=1;i--){
        for(int j=A[i]+1;j<=ed[in(A[i])];j++) sum[j]++;
        for(int j=in(A[i])+1;j<=in(len);j++) Tg[j]++;
        for(auto j:Lc[i]) for(int k=j.st;k>=j.ed;k--)
            ans[j.id]+=j.sgn*(sum[A[k]]+Tg[in(A[k])]);
    }
    for(int i=1;i<=m;i++) ans[Qr[i].id]+=ans[Qr[i-1].id];
    for(int i=1;i<=m;i++) Wt(ans[i]),pc('\n');
    return 0;
}

T 3 : T3: T3: Y u n o Yuno Yuno l o v e s loves loves s q r t sqrt sqrt t e c h n o l o g y technology technology I I I III III

给你一个长为 n n n的序列 a a a m m m次询问,每次查询一个区间的众数的出现次数,强制在线。

1 ≤ n , m ≤ 5 × 1 0 5 , 0 ≤ a i ≤ 1 0 9 。 1\leq n,m\leq 5\times 10^5,0\leq a_i\leq 10^9。 1n,m5×1050ai109

考虑序列分块。对于询问 [ l , r ] [l,r] [l,r],记 l l l所在块为 s s s r r r所在块为 t t t,则可以先预处理出 [ s + 1 , t − 1 ] [s+1,t-1] [s+1,t1]的区间众数。具体求法就是从一个块 i i i开始往后扫描依次加入每个数,更新每个数出现次数时和答案取 m a x max max即可。然后区间众数有一个性质:要么是 [ s + 1 , t − 1 ] [s+1,t-1] [s+1,t1]的答案,要么在散块中出现。这也很好证,如果一个数不是 [ s + 1 , t − 1 ] [s+1,t-1] [s+1,t1]的众数,而且不在散块中出现,显然出现次数比 [ s + 1 , t − 1 ] [s+1,t-1] [s+1,t1]的众数少。下面只要求出散块里每个数在 [ s + 1 , t − 1 ] [s+1,t-1] [s+1,t1]出现次数,和答案取 m a x max max就好了,注意到一共有 n \sqrt{n} n 级别的数,要求 O ( 1 ) O(1) O(1)查询在 [ s + 1 , t − 1 ] [s+1,t-1] [s+1,t1]出现次数。显然前缀和可以做到,但是空间不够。于是有一个我认为很巧妙的方法可以实现:对每个数开一个 v e c t o r vector vector,把它出现的所有位置插入这个 v e c t o r vector vector,然后记录每个位置在 v e c t o r vector vector里的下标,记为 p s [ i ] ps[i] ps[i]。查询时一开始令 a n s ans ans等于 [ s + 1 , t − 1 ] [s+1,t-1] [s+1,t1]的众数出现次数,然后对应左边散块的每个位置 i i i,只要 p s [ i ] + a n s ps[i]+ans ps[i]+ans也在 [ l , r ] [l,r] [l,r]中,就令 a n s + 1 ans+1 ans+1。对应右边散块的每个位置 i i i,只要 p s [ i ] − a n s ps[i]-ans ps[i]ans也在 [ l , r ] [l,r] [l,r]中,就令 a n s + 1 ans+1 ans+1。然后就做完了,时间复杂度 O ( n n ) O(n\sqrt{n}) O(nn ),空间复杂度 O ( n ) O(n) O(n),常数很小。

#include<iostream>
#include<vector>
#include<algorithm>
#include<string.h>
#define gc getchar
#define pc putchar
#define pb push_back
#define f first
#define s second
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
template<class Typ> Typ &Rd(Typ &x){
	char ch=gc(),sgn=0; x=0;
	for(;ch<'0'||ch>'9';ch=gc()) sgn|=ch=='-';
	for(;ch>='0'&&ch<='9';ch=gc()) x=x*10+(ch^48);
	return sgn&&(x=-x),x;
}
template<class Typ> void Wt(Typ x){
	if(x<0) pc('-'),x=-x;
	if(x>9) Wt(x/10);
	pc(x%10^48);
}
const int N=5e5+5;
const int Len=750;
int n,m,A[N],Ans[Len][Len],ps[N],ans;
int cnt[N],st[Len],ed[Len],in[N];
int Num[N],len; vector<int> Id[N];
int main(){
	Rd(n),Rd(m);
	for(int i=1;i<=n;i++) Num[++len]=Rd(A[i]);
	sort(Num+1,Num+len+1);
	len=unique(Num+1,Num+len+1)-Num-1;
	for(int i=1;i<=n;i++)
		A[i]=lower_bound(Num+1,Num+len+1,A[i])-Num;
	for(int i=1;i<=n;i++)
		Id[A[i]].pb(i),ps[i]=Id[A[i]].size()-1;
	for(int i=1;i<=n;i++) in[i]=(i-1)/Len+1;
	for(int i=1;i<=in[n];i++)
		st[i]=(i-1)*Len+1,ed[i]=min(i*Len,n);
	for(int i=1;i<=in[n];i++){
		for(int j=i;j<=in[n];j++){
			Ans[i][j]=Ans[i][j-1];
			for(int k=st[j];k<=ed[j];k++)
				Ans[i][j]=max(Ans[i][j],++cnt[A[k]]);
		}
		memset(cnt,0,sizeof(cnt));
	}
	for(int i=1;i<=m;i++){
		int L,R; Rd(L)^=ans,Rd(R)^=ans;
		if(in[R]-in[L]<=1){
			ans=0;
			for(int j=L;j<=R;j++)
				ans=max(ans,++cnt[A[j]]);
			for(int j=L;j<=R;j++) cnt[A[j]]--;
			Wt(ans),pc('\n');
		}else{
			ans=Ans[in[L]+1][in[R]-1];
			for(int i=L;i<=ed[in[L]];i++)
				while(ps[i]+ans<Id[A[i]].size()
					&&Id[A[i]][ps[i]+ans]<=R) ans++;
			for(int i=st[in[R]];i<=R;i++)
				while(ps[i]-ans>=0&&Id[A[i]][ps[i]-ans]>=L) ans++;
			Wt(ans),pc('\n');
		}
	}
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值