分块八:询问等于一个数 c 的元素,并将这个区间的所有元素改为 c
维护每个分块是否有统一的值 num[],如果有,块的时间是o(1),否则暴力
//AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,t,ans;
int l[N],r[N],pos[N],siz[N],a[N],vis[N],zhi[N];
void kuai(){
t=sqrt(n);
for(int i=1;i<=t;i++){
l[i]=(i-1)*t+1;
r[i]=i*t;
siz[i]=t;
}
if(r[t]<n){
t++;
l[t]=r[t-1]+1;
r[t]=n;
siz[t]=n-r[t-1];
}
for(int i=1;i<=t;i++)
for(int j=l[i];j<=r[i];j++)
pos[j]=i;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
kuai();
int ll,rr,L,R,c;
for(int i=1;i<=n;i++){
scanf("%d%d%d",&ll,&rr,&c);
L=pos[ll];R=pos[rr];ans=0;
if(L==R){
if(!vis[L]){
for(int j=ll;j<=rr;j++){
if(a[j]==c) ans++;
else a[j]=c;
}
}
else{
if(zhi[L]==c) ans+=rr-ll+1;
else{
if(ll==l[L]&&rr==r[R]) zhi[L]=c;
else{
vis[L]=0;
for(int j=ll;j<=rr;j++)
a[j]=c;
for(int j=l[L];j<ll;j++)
a[j]=zhi[L];
for(int j=rr+1;j<=r[R];j++)
a[j]=zhi[R];
}
}
}
}
else{
for(int j=L+1;j<=R-1;j++){
if(!vis[j]){
for(int k=l[j];k<=r[j];k++){
if(a[k]==c) ans++;
else a[k]=c;
}
vis[j]=1;zhi[j]=c;
}
else{
if(zhi[j]==c) ans+=siz[j];
else zhi[j]=c;
}
}
if(!vis[L]){
for(int j=ll;j<=r[L];j++){
if(a[j]==c) ans++;
else a[j]=c;
}
}
else{
if(zhi[L]==c) ans+=r[L]-ll+1;
else{
vis[L]=0;
for(int j=ll;j<=r[L];j++)
a[j]=c;
for(int j=l[L];j<ll;j++)
a[j]=zhi[L];
}
}
if(!vis[R]){
for(int j=l[R];j<=rr;j++){
if(a[j]==c) ans++;
else a[j]=c;
}
}
else{
if(zhi[R]==c) ans+=rr-l[R]+1;
else{
vis[R]=0;
for(int j=l[R];j<=rr;j++)
a[j]=c;
for(int j=rr+1;j<=r[R];j++)
a[j]=zhi[R];
}
}
}
printf("%d\n",ans);
}
return 0;
}
//代码有锅
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100010
int a[MAXN],l[1010],r[1010],num[1010],pos[MAXN],n,x,y,z,m;
//num表示当前块是否有统一值
int main()
{
scanf("%d",&n);
m=sqrt(n);
for(int i=1;i<=n;i++)//初始分块
{
scanf("%d",&a[i]);
pos[i]=(i-1)/m+1;
l[pos[i]]=min(l[pos[i]],i);
r[pos[i]]=max(r[pos[i]],i);
}
int k=n;
while(k--)
{
scanf("%d%d%d",&x,&y,&z);
int ans=0;
if(pos[y]-pos[x]<=1)
{
for(int i=x;i<=y;i++)
{
if(a[i]==z) ans++;
a[i]=z;
}
}
else
{
for(int i=pos[x]+1;i<=pos[y]-1;i++)
{
if(num[i]==z) ans+=r[i]-l[i]+1;
else if(!num[i])
{
for(int i=l[i];i<=r[i];i++)
{
if(a[i]==z) ans++;
a[i]=z;
}
}
num[i]=z;
}
//左残缺块
if(num[pos[x]]==z)
{
ans+=r[pos[x]]-x+1;
}
else
{
if(num[pos[x]])//修改l之前的值
for(int i=l[pos[x]];i<=x-1;i++)
{
a[i]=num[pos[x]];
}
for(int i=x;i<=r[pos[x]];i++)
{
if(a[i]==z) ans++;
a[i]=z;
}
num[pos[x]]=0;
}
//右残缺块
if(num[pos[y]]==z)
{
ans+=y-l[pos[y]]+1;
}
else
{
if(num[pos[y]])//修改l之前的值
for(int i=y+1;i<=r[pos[y]];i++)
{
a[i]=num[pos[y]];
}
for(int i=y;i>=l[pos[y]];i--)
{
if(a[i]==z) ans++;
a[i]=z;
}
num[pos[y]]=0;
}
}
printf("%d\n",ans);
}
return 0;
}
分块九:区间众数 (P4168 [Violet]蒲公英)
洛谷题解:https://www.luogu.com.cn/problem/solution/P4168
首先暴力统计区间众数的时间复杂度为接近O(n^2),时间复杂度不够优秀,所以我们 遇事不决先分块考虑分段处理。数据范围比较大,所以我们离散化。所谓离散化就是将数据排好序后用ta的排名来代替ta本身(需要另开一个数组)。nlog(n)时间内就能完成。
void yych()//离散化
{
len=sqrt(n);
memset(L,0x3f,sizeof(L));
for(int i=1;i<=n;++i)
{
pos[i]=(i-1)/len+1;
L[pos[i]]=min(L[pos[i]],i);
R[pos[i]]=max(R[pos[i]],i);
a[i]=read();
b[i]=a[i];
}
sort(b+1,b+1+n);
tot=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+1+tot,a[i])-b;
}
然后我们考虑分块预处理。我们首先可以处理出每个数字在块中出现次数的前缀和。枚举每一个数是O(n),枚举每一个块时间是O( n \sqrt{n} n)),所以总的时间就是O(n n \sqrt {n} n)。然后我们还可以处理出从第l个块到第r个块的区间众数。我们枚举l的复杂度是O( n \sqrt {n} n),枚举r的复杂度是O( n \sqrt {n} n),然后枚举块内每一个数的时间复杂度也是O( n \sqrt {n} n),总共就是O(n( n \sqrt {n} n))
其中非常重要的就是
void Treaker()//分块预处理
{
for(int i=1;i<=n;++i)
for(int j=pos[i];j<=pos[n];++j)s[j][a[i]]++;
for(int i=1;i<=pos[n];++i)
{
memset(tong,0,sizeof(tong));
for(int j=i;j<=pos[n];++j)
{
p[i][j]=p[i][j-1];
for(int k=L[j];k<=R[j];++k)
{
tong[a[k]]++;
if((tong[a[k]]>tong[p[i][j]])||(tong[a[k]]==tong[p[i][j]]&&a[k]<p[i][j]))p[i][j]=a[k];
}
}
}
memset(tong,0,sizeof(tong));
}
其中非常重要的就是
p[i][j]=p[i][j-1];
因为我们首先需要继承原先的区间众数,然后才能够比较更新。否则的话就会 听取WA声一片,我就是有很多次挂在这里的 TAT.
然后就是询问操作,我们根据分块直觉,就应该是残块暴力搞,整块骚操作。所以我们对于
p
o
s
r
−
p
o
s
l
≤
2
pos_r- pos_l \le2
posr−posl≤2的情况直接暴力就OK了。
if(pos[r]-pos[l]<=2)
{
int res=0;
for(int i=l;i<=r;++i)
{
++tong[a[i]];
if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]<res))res=a[i];
}
for(int i=l;i<=r;++i)tong[a[i]]=0;
return res;
}
对于其他的情况,我们知道我们所求的答案,要么是整块里的众数,要么是残块里的数字。对于残块里的数字,以为我们之前已经处理过了整块的数字出现的前缀和,所以我们能统计出残块中的数字在询问区间里的出现次数。对于整块中的众数,我们也能在 O ( n ) O ( n ) O(\sqrt n)O( n) O(n)O(n)复杂度内统计出 ta在残块中的出现次数。这样我们就能得到我们询问区间里众数。
int res=0;
for(int i=l;i<=R[pos[l]];++i)if(!tong[a[i]])tong[a[i]]+=s[pos[r]-1][a[i]]-s[pos[l]][a[i]];
for(int i=L[pos[r]];i<=r;++i)if(!tong[a[i]])tong[a[i]]+=s[pos[r]-1][a[i]]-s[pos[l]][a[i]];
for(int i=l;i<=R[pos[l]];++i)
{
++tong[a[i]];
if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]<res))res=a[i];
}
for(int i=L[pos[r]];i<=r;++i)
{
++tong[a[i]];
if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]<res))res=a[i];
}
int k=p[pos[l]+1][pos[r]-1];
int lin=s[pos[r]-1][k]-s[pos[l]][k];
for(int i=l;i<=R[pos[l]];++i)lin+=(a[i]==k);
for(int i=L[pos[r]];i<=r;++i)lin+=(a[i]==k);
if(lin>tong[res]||(lin==tong[res]&&k<res))res=k;
for(int i=l;i<=R[pos[l]];++i)tong[a[i]]=0;
for(int i=L[pos[r]];i<=r;++i)tong[a[i]]=0;
return res;
还有一点就是我们在统计每个数字出现的次数的时候我们需要开一个桶数组,我们每次用完了之后不用全部清空,只用将我们使用过的清空一下就可以啦。实测可以省掉很大的时间。
最后说一下注意事项:
1.注意离散化,否则直接RE到原地起飞。
2.注意p数组的转移,否则WA到怀疑人生。
3.注意分块的边界,否则TLE到直接原地爆炸。
最后献上窝丑陋的代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,tot,len,l,r,ans;
const int N=40010,M=50010,K=510;
int a[N],b[N],pos[N],s[K][N],L[K],R[K],p[K][K],tong[N];
int read()
{
char ch;int x=0,f=1;
while(!isdigit(ch=getchar()))
{(ch=='-')&&(f=-f);}
while(isdigit(ch))
{x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int cnt=0;
void yych()//离散化
{
len=sqrt(n);//分成根号n块
memset(L,0x3f,sizeof(L));
for(int i=1;i<=n;++i)
{
pos[i]=(i-1)/len+1;//第i个元素所在的块
L[pos[i]]=min(L[pos[i]],i);//每块的左端点
R[pos[i]]=max(R[pos[i]],i);
a[i]=read();
b[i]=a[i]; //离散化数组
}
sort(b+1,b+1+n);
tot=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+1+tot,a[i])-b;
}
void Treaker()//分块预处理
{
for(int i=1;i<=n;++i)
for(int j=pos[i];j<=pos[n];++j)
s[j][a[i]]++;//类似于前缀和数组,第j块的a[i]++,因为是前缀和,后面的块都需要加
//p[i][j]表示i到j块的众数
for(int i=1;i<=pos[n];++i)
{
memset(tong,0,sizeof(tong));//i变化后,桶内元素重置
for(int j=i;j<=pos[n];++j)
{
p[i][j]=p[i][j-1];//继承上一个
for(int k=L[j];k<=R[j];++k)//统计第j块
{
tong[a[k]]++;
//更优的情况
if((tong[a[k]]>tong[p[i][j]])||(tong[a[k]]==tong[p[i][j]]&&a[k]<p[i][j]))p[i][j]=a[k];
}
}
}
memset(tong,0,sizeof(tong));
}
int ask(int l,int r)
{
if(pos[r]-pos[l]<=2)//区间没有完整块的情况,暴力
{
int res=0;
for(int i=l;i<=r;++i)
{
++tong[a[i]];
if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]<res))res=a[i];
}
for(int i=l;i<=r;++i)tong[a[i]]=0;//注意清零
return res;
}
//有完整块的情况 ,分成残缺块和完整块两种情况。
int res=0;
//如果桶内无元素,则把残缺块的元素要加上完整块的个数
for(int i=l;i<=R[pos[l]];++i)if(!tong[a[i]])tong[a[i]]+=s[pos[r]-1][a[i]]-s[pos[l]][a[i]];//前缀和
for(int i=L[pos[r]];i<=r;++i)if(!tong[a[i]])tong[a[i]]+=s[pos[r]-1][a[i]]-s[pos[l]][a[i]];
for(int i=l;i<=R[pos[l]];++i)//左残缺块
{
++tong[a[i]];
if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]<res))res=a[i];
}
for(int i=L[pos[r]];i<=r;++i)//右残缺块
{
++tong[a[i]];
if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]<res))res=a[i];
}
int k=p[pos[l]+1][pos[r]-1];//完整块的众数
int lin=s[pos[r]-1][k]-s[pos[l]][k];//完整块的众数个数
for(int i=l;i<=R[pos[l]];++i)lin+=(a[i]==k);//k需要加上残缺块
for(int i=L[pos[r]];i<=r;++i)lin+=(a[i]==k);
if(lin>tong[res]||(lin==tong[res]&&k<res))res=k;
for(int i=l;i<=R[pos[l]];++i)tong[a[i]]=0; //清零
for(int i=L[pos[r]];i<=r;++i)tong[a[i]]=0;
return res;
}
int main()
{
cin>>n>>m;
yych();
Treaker();
for(int i=1;i<=m;++i)
{
l=read();r=read();
l=(l+ans-1)%n+1;r=(r+ans-1)%n+1;
if(l>r)swap(l,r);
ans=b[ask(l,r)];
printf("%d\n",ans);
}
return 0;
}