2023

教主的魔法

大水题

注意开long long。另开数组\(b\),分块排序,然后修改的时候整块打标记,散块暴力重构

查询的时候散块暴力,整块倍增就OK

#define N 1050050
#define int long long
int L[N],R[N],siz,block,t,a[N],tag[N],b[N],pos[N],n,m;
void get(int id){
	if(tag[id]!=0){
		for(int i=L[id];i<=R[id];i++)a[i]+=tag[id];
	}
	for(int i=L[id];i<=R[id];i++)b[i]=a[i];
	sort(b+L[id],b+R[id]+1);
} 
void init(){
	cin>>n>>m;block=sqrt(n),siz=(n+block-1)/block;t=log(n)/log(2)+1;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=siz;i++){
		L[i]=R[i-1]+1,R[i]=min(L[i]+block-1,n);
		for(int j=L[i];j<=R[i];j++){
			pos[j]=i,b[j]=a[j];
		}
	}
	for(int i=1;i<=siz;i++)sort(b+L[i],b+R[i]+1);
}
void change(int l,int r,int k){
	if(pos[l]==pos[r]){
		for(int i=l;i<=r;i++)a[i]+=k;
		get(pos[l]);return ;
	}
	for(int i=l;i<=R[pos[l]];i++)a[i]+=k;
	for(int i=L[pos[r]];i<=r;i++)a[i]+=k;
	for(int i=pos[l]+1;i<pos[r];i++)tag[i]+=k;
	get(pos[l]);get(pos[r]);
}
int query(int l,int r,int k){
	int res=0;
	if(pos[l]==pos[r]){
		for(int i=l;i<=r;i++)res+=(a[i]+tag[pos[l]]>=k);
		return res;
	}
	for(int i=l;i<=R[pos[l]];i++)res+=(a[i]+tag[pos[l]]>=k);
	for(int i=L[pos[r]];i<=r;i++)res+=(a[i]+tag[pos[r]]>=k);
	for(int i=pos[l]+1;i<pos[r];i++){
		int p=L[i]-1;
		for(int j=t;j>=0;--j){
			if(p+(1<<j)>R[i])continue;
			if(b[p+(1<<j)]+tag[i]<k)p+=(1<<j);
		}
		res+=R[i]-p;
	}
	return res;
}
signed main(){
	ios::sync_with_stdio(false);
	init();
	while(m--){
		char opt;int l,r,k;
		cin>>opt>>l>>r>>k;
		if(opt=='M'){
			change(l,r,k);
		}
		else cout<<query(l,r,k)<<"\n";
	}
	return 0;
} 

作诗

分块超级大暴力。

预处理每个数载每个块的出现次数,求个前缀和。预处理每个块区间的答案

查询的时候小块暴力,否则先拿到中间超级大块的答案,两边散块结合前缀和分类讨论乱搞即可

具体地:

\(sum[i,j]\)表示前\(i\)块中数字\(j\)的出现次数。做法显然

\(ans[l,r]\)表示第\(l\)块到第\(r\)块的答案,做法是:进行递推求解,

  1. \(ans[l,r]=ans[l,r-1]\)
  2. 枚举\([L[r],R[r]]\)中的每个数,查询这个数在\([L[l],R[r-1]]\)的出现次数与\([L[l],R[r]]\)的出现次数,分类讨论更新答案(记得只能统计一次,可以通过vis实现,记得撤销)
  3. 大功告成

对于答案的查询,首先\(Ans=ans[pos[l]+1,pos[r]-1]\)肯定没有任何问题。

然后将在\([l,R[pos[l]]],[L[pos[r]],r]\)中出现过的数在大块\([pos[l]+1,pos[r]-1]\)的出现次数预处理进cnt,然后统计答案就类似于预处理的第2步,具体可以看代码

void init(){
	cin>>n>>c>>m;++c;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)a[i]++;
	block=sqrt(n),siz=(n+block-1)/block;
	for(int i=1;i<=siz;i++)L[i]=R[i-1]+1,R[i]=min(n,L[i]+block-1);
	for(int i=1;i<=siz;i++)for(int j=L[i];j<=R[i];j++)pos[j]=i;
	for(int i=1;i<=siz;i++){
		for(int j=1;j<=c;j++)sum[i][j]=sum[i-1][j];
		for(int j=L[i];j<=R[i];j++)sum[i][a[j]]++;
	}
	for(int i=1;i<=siz;i++){
		for(int j=L[i];j<=R[i];j++){
			int k=cnt[a[j]];
			if(k&1)ans[i][i]++;
			else if(k>0)ans[i][i]--;
			cnt[a[j]]++;
		}
		for(int j=L[i];j<=R[i];j++)cnt[a[j]]=0;
	}
	for(int i=1;i<=siz;i++){
		for(int j=i+1;j<=siz;j++){
			ans[i][j]=ans[i][j-1];
			for(int k=L[j];k<=R[j];k++){
				if(vis[a[k]])continue;
				vis[a[k]]=1;
				int pre=sum[j-1][a[k]]-sum[i-1][a[k]];
				int now=sum[j][a[k]]-sum[j-1][a[k]];
				if(!pre){
					if(now>0&&now%2==0)ans[i][j]++;
                }
				else if(pre&1) {
					if(now&1)ans[i][j]++;
                }
				else if(now&1)ans[i][j]--;
			}
			for(int k=L[j];k<=R[j];k++)vis[a[k]]=0;
		}
	}
}
int solve(int l,int r){
	int Ans=0;
	if(pos[l]==pos[r]){
		for(int i=l;i<=r;i++){
			int k=cnt[a[i]];
			if(k&1)Ans++;
			else if(k>0)Ans--;
			cnt[a[i]]++; 
		}
		for(int i=l;i<=r;i++)cnt[a[i]]=0;
		return Ans;
	}
	Ans=ans[pos[l]+1][pos[r]-1];
	for(int i=l;i<=R[pos[l]];i++)cnt[a[i]]=sum[pos[r]-1][a[i]]-sum[pos[l]][a[i]];
	for(int i=L[pos[r]];i<=r;i++)cnt[a[i]]=sum[pos[r]-1][a[i]]-sum[pos[l]][a[i]];
	for(int i=l;i<=R[pos[l]];i++){
		int k=cnt[a[i]];
		if(k&1)Ans++;
		else if(k>0)Ans--;
		cnt[a[i]]++; 
	}
	for(int i=L[pos[r]];i<=r;i++){
		int k=cnt[a[i]];
		if(k&1)Ans++;
		else if(k>0)Ans--;
		cnt[a[i]]++; 
	}
	for(int i=l;i<=R[pos[l]];i++)cnt[a[i]]=0;
	for(int i=L[pos[r]];i<=r;i++)cnt[a[i]]=0;
	return Ans;
} 

小Z的袜子

对于一个区间,设\(cnt[i]\)表示数\(i\)出现次数,\(len=r-l+1\)

则:

\[Ans=\frac{\sum_{i=0}^V{cnt[i]\choose 2}}{len\choose 2} \]

\({len\choose 2}\)是常数,剔除。然后考虑上面的怎么做。

根据二项式系数的性质:

  1. \({n\choose m}=\sum_{i=0}^n{i\choose m-1}\)

  2. \({n\choose 1}=n\)

可以将分子拆为:\(\sum_{i=0}^V\sum_{j=0}^{cnt[i]}j\)

这样就可以愉快地莫队啦!

记得用gcd化简哦

void add(int x){
	res+=cnt[a[x]];++cnt[a[x]];
}
void del(int x){
	--cnt[a[x]];res-=cnt[a[x]];
}
void solve(){
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		while(l>ask[i].l)add(--l);
		while(r<ask[i].r)add(++r);
		while(l<ask[i].l)del(l++);
		while(r>ask[i].r)del(r--);
		int len=r-l+1;
		int d=gcd(res,len*(len-1)/2);
		ans1[ask[i].id]=res/d;ans2[ask[i].id]=len*(len-1)/2/d;
	}
	for(int i=1;i<=m;i++)cout<<ans1[i]<<"/"<<ans2[i]<<"\n";
}

HH的项链

莫队板题,没啥好说

void add(int x){
	++cnt[a[x]];if(cnt[a[x]]==1)res++;
}
void del(int x){
	--cnt[a[x]];if(cnt[a[x]]==0)res--;
}

蒲公英

昨天的T9,强制在线,就不能写莫队了

一样,先预处理整块区间答案,然后散块边搞边更新

   	
int get(int x){
   return x%block==0?x/block:x/block+1;
}
inline void init(){
   scanf("%d%d",&n,&m);
   block=sqrt(n),siz=n%block==0?n/block:n/block+1;
   for(int i=1;i<=n;i++)
       scanf("%d",&a[i]),c[i]=a[i];
   sort(a+1,a+n+1);
   int q=unique(a+1,a+n+1)-a-1;
   for(int i=1;i<=n;i++){
   	int pos=lower_bound(a+1,a+q+1,c[i])-a;
   	b[pos]=c[i];
   	c[i]=pos;
   }
   for(int i=1;i<=siz;i++){
       memset(cnt,0,sizeof(cnt));
   	node x;
       x.num=x.s=0;
       for(int j=i;j<=siz;j++){
           for(int k=(j-1)* block+1;k<=min(n,j * block);k++){
               cnt[c[k]]++;
               if((cnt[c[k]]>x.s)||(cnt[c[k]]==x.s&&c[k]<x.num)){
                   x.num=c[k];
                   x.s=cnt[c[k]];
               }
           }
           p[i][j]=x;
       }
   }
   for(int i=1;i<=siz;i++){
       for(int j=1;j<=q;j++)sum[i][j]=sum[i-1][j];
       for(int j=(i-1)* block+1;j<=min(n,i * block);j++)sum[i][c[j]]++;
   }
}
int solve(int l,int r){
   int ans=0;
   int L=get(l),R=get(r);
       if(R-L<=2){
       for(int j=l;j<=r;j++)tot[c[j]]=0;
       for(int j=l;j<=r;j++){
           tot[c[j]]++;
           if(tot[c[j]]>tot[ans]||(tot[c[j]]==tot[ans]&&ans>c[j]))ans=c[j];
       }
   } 
   else {
       ans=p[L+1][R-1].num;
   	tot[ans]=0,vis[ans]=0;
       for(int j=l;j<=min(n,L * block);j++)tot[c[j]]=0,vis[c[j]]=0;
       for(int j=(R-1)* block+1;j<=r;j++)tot[c[j]]=0,vis[c[j]]=0;
       for(int j=l;j<=min(n,L * block);j++)tot[c[j]]++;
       for(int j=(R-1)* block+1;j<=r;j++)tot[c[j]]++;
       int id,mx=0;
       for(int j=l;j<=min(n,L * block);j++){
           if(!vis[c[j]]){
               vis[c[j]]=1;
               int val=tot[c[j]]+sum[R-1][c[j]]-sum[L][c[j]];
               if(mx < val||(mx==val&&id>c[j]))mx=val,id=c[j];
           }
       }
       for(int j=(R-1)* block+1;j<=r;j++){
           if(!vis[c[j]]){
               vis[c[j]]=1;
               int val=tot[c[j]]+sum[R-1][c[j]]-sum[L][c[j]];
               if(mx < val||(mx==val&&id>c[j]))mx=val,id=c[j];
           }
       }
       if(mx>tot[ans]+p[L+1][R-1].s||(mx==tot[ans]+p[L+1][R-1].s&&ans>id))ans=id;
   }
   last=b[ans];
   return last;
}

由乃打扑克

Ynoi里唯一的良心题。参考昨天T2和,然后将求第\(k\)大化为二分即可,可以参考T1的做法,下面分代码讲解

复杂度\(O(n\log n\sqrt n)\)

具体地:

inline void init(){
	read(n);read(m);
	tt=log(block)/log(2)+1;siz=(n+block-1)/block;
	for(re int i=1;i<=n;i++)read(a[i]);
	for(re int i=1;i<=n;i++)b[i]=a[i];
	for(re int i=1;i<=siz;i++)L[i]=R[i-1]+1,R[i]=L[i]+block-1;
	R[siz]=n;
	for(re int i=1;i<=siz;i++)for(re int j=L[i];j<=R[i];j++)pos[j]=i;
	for(re int i=1;i<=siz;i++)sort(b+L[i],b+R[i]+1);
}
inline void init_only(int id){
	for(re int i=L[id];i<=R[id];i++)b[i]=a[i]+tag[id];
	for(re int i=L[id];i<=R[id];i++)a[i]+=tag[id];
	sort(b+L[id],b+R[id]+1);
	tag[id]=0;
}
inline void change(int l,int r,int k){
	if(pos[l]==pos[r]){
		for(re int i=l;i<=r;i++)a[i]+=k;
		init_only(pos[l]);
		return ;
	}
	for(re int i=pos[l]+1;i<pos[r];i++)tag[i]+=k;
	for(re int i=l;i<=R[pos[l]];i++)a[i]+=k;
	for(re int i=L[pos[r]];i<=r;i++)a[i]+=k; 
	init_only(pos[l]);init_only(pos[r]);
}

对于求排名,大块二分/倍增,小块暴力即可

大块有一个优化是提前判断一下本块最小值/最大值,如果最小值都比查找的值大直接跳,最大值比它小直接加上贡献跳过,可以避免二分一个块。加上这玩意跑得飞起。事实上随机数据下这个优化是很强的。

然后将第\(k\)大使用二分化为求一个数的排名。

inline int rk(int l,int r,int k){
	re int res=0;
	if(pos[l]==pos[r]){
		for(int i=l;i<=r;i++)if(a[i]+tag[pos[l]]<k)res++;
		return res;
	}
	for(re int i=l;i<=R[pos[l]];i++)if(a[i]+tag[pos[l]]<k)res++;
	for(re int i=L[pos[r]];i<=r;i++)if(a[i]+tag[pos[r]]<k)res++;
	for(re int i=pos[l]+1;i<pos[r];i++){
		re int l=L[i]-1,c=k-tag[i]-1;
		if(b[l+1]>c)continue;
		if(b[R[i]]<=c){
		    res+=R[i]-L[i]+1;
		    continue;
		}
		for(re int p=tt;p>=0;--p){
			if(l+(1<<p)>R[i])continue;
			if(b[l+(1<<p)]<=c)l+=(1<<p); 
		}
		res+=(l-L[i]+1);
	}
	return res;	
}

注意这题有点小卡,所以需要提前限定二分范围,因为注意到题目说了每次改动不会超过\(|2\times 10^4|\),所以设\(T\)表示改了几次,二分边界就出来了。

为了怕被卡,可以将\(T\)提前设为2或者直接加2

int query(int l,int r,int k){
	re int L=-(10000*(T+3))<<1,R=(10000*(T+2))<<1;
	while(L<R){
		re int mid=L+R+1>>1;
		if(rk(l,r,mid)+1>k)R=mid-1;
		else L=mid; 
	}
	if(L==-(10000*(T+3))<<1||L==(10000*(T+2))<<1)return -1;
	return L;
}
signed main(){
	ios::sync_with_stdio(false);
	init();
	while(m--){
		re int opt,l,r,k;read(opt);read(l);read(r);read(k);
		if(l>r)swap(l,r);
		if(opt==2)change(l,r,k),++T;
		else {
			cout<<query(l,r,k)<<"\n";
		}
	}
}

注意利用评测机二分最优块长后,我发现块长设为330左右跑得很快

posted @ 2023-03-05 07:39  spdarkle  阅读(76)  评论(0编辑  收藏  举报