分块基本 + 多维后缀和

1.区间众数

首先,预处理二维数组 f[i][j]表示,前 i 块中 j 的出现次数, 那么对于整块来说,f[r][j] - f[l - 1][j] 就表示 [l, r]块中,j 的出现次数

其次,预处理二维数组 g[i][j]表示,在第 i 块 到 第 j 块 中,出现最多的数的次数

这样,在查询时,只需要遍历一遍零散的块就够了,然后取 max

代码
#include<bits/stdc++.h>

const int K = 9; 
const int MAXN = 6e4 + 10;
using namespace std;

int T;
int n,m;
int ans,shu;
int last;
int size,zs;
int cnt[MAXN];
struct node
{
	int val;
	int ord;
};
node k[MAXN];
int fi[MAXN];
int f[300][300];
int g[300][MAXN];
int st[300],en[300];

bool cmp(node a,node b)
{
	return a.val < b.val;
}

void up(int &a,int b)
{
	a = a > b ? a : b;
}
void init()
{
	// init f
	for(int i=0;i<=zs;i++)
		for(int j=0;j<=zs;j++)
			f[i][j] = 0;
	for(int i=0;i<=zs;i++)
	{ 
		for(int j=1;j<=shu;j++) cnt[j] = 0;
		int cntt = 0;
		for(int j=i;j<=zs;j++)
		{
			for(int l=st[j];l<=en[j];l++)
				up(cntt,++cnt[fi[l]]);
			f[i][j] = cntt;
		}
	}
	//init g
	for(int i=0;i<=zs;i++)
		for(int j=1;j<=shu;j++)	
			g[i][j] = 0;	
	for(int i=1;i<=shu;i++) cnt[i] = 0;
	for(int i=0;i<=zs;i++)
	{
		for(int j=st[i];j<=en[i];j++) cnt[fi[j]]++;
		for(int j=1;j<=shu;j++) g[i][j] = cnt[j];
	}
}

int ask(int l,int r)
{
	int L = (l >> K) ;
	int R = (r >> K) ;
	int res = 0;
	if(L + 1 >= R)
	{
		for(int i=1;i<=shu;i++) cnt[i] = 0;
		for(int i=l;i<=r;i++)
		{
			up(res,++cnt[fi[i]]);
		}
		return res;
	}
	res = f[L+1][R-1];
	for(int i=1;i<=shu;i++) cnt[i] = 0;
	for(int i=en[L];i>=l;i--)
	{
		up(res,(++cnt[fi[i]]) + g[R-1][fi[i]] - g[L][fi[i]]);
	}
	for(int i=st[R];i<=r;i++)
	{
		up(res,(++cnt[fi[i]]) + g[R-1][fi[i]] - g[L][fi[i]]);
	}
	return res;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		ans = 0;
		for(int i=1;i<=n;i++) cin>>k[i].val,k[i].ord = i;
		sort(k+1,k+n+1,cmp);
		shu = 0;
		for(int i=1;i<=n;i++)
		{
			if(k[i].val > k[i-1].val) shu++;
			fi[k[i].ord] = shu;
		}
		zs = n >> K;
		for(int i=1;i<=n;i++) en[(i>>K)] = i;
		for(int i=n;i>=1;i--) st[(i>>K)] = i;
		init();
		int l,r;
		for(int i=1;i<=m;i++)
		{
			cin>>l>>r;
			ans = ask(l^ans,r^ans);
			cout<<ans<<'\n';
		}
//		cout<<size<<" "<<p<<"\n";
//		for(int i=0;i<=zs;i++) cout<<st[i]<<" ";
//		cout<<'\n';
//		for(int i=0;i<=zs;i++) cout<<en[i]<<" ";
	}
	return 0;
}

2.区间第k小

涉及两个操作: 对某区间都加一个数, 查询某区间内第k小的数

对第一个操作:定义一个数组用来记录某一块整体都加了多少,涉及到零散的,就单独遍历,加上这个数后再重新排序

对第二个操作:可以二分,假设这个第k小的数是mid,在每个块(预先排好序的)里二分,看是不是第k小的。

查看代码
 #include<bits/stdc++.h>


const int K = 9;
const int MAXN = 5e4 + 10;
using namespace std;

int T;
int n,m;
struct node
{
	int val;
	int ord;
};
int st[310],en[310];
node k[MAXN];
node kk[MAXN];
int seg[310];
int op,ll,rr,s;

bool cmp(node a,node b){return a.val < b.val;}

void add(int l,int r,int x)
{

	int L = l >> K;
	int R = r >> K;
	for(int i = L + 1;i <= R - 1;i++) seg[i] += x;
	node a[1010],b[1010];
	
	int cnt1 = 0,cnt2 = 0;
	for(int i = st[L];i <= en[L];i++)
	{
		if(kk[i].ord >= l && kk[i].ord <= r)
		{
			kk[i].val += x;
			k[kk[i].ord] = kk[i];
			a[++cnt1] = kk[i];
		}
		else
		{
			b[++cnt2] = kk[i];
		} 
	}
	int poi = st[L];
	int i = 1,j = 1;
	while(i <= cnt1 && j <= cnt2)
	{
		node aa = a[i];
		node bb = b[j];
		if(aa.val > bb.val) 
		{
			kk[poi] = bb;
			j++;
		}
		else 
		{
			kk[poi] = aa;
			i++;
		}
		poi++;
	}
	while(i <= cnt1) kk[poi++] = a[i++];
	while(j <= cnt2) kk[poi++] = b[j++];
	
	if(L == R) return ;
	
	i = j = 1;
	cnt1 = cnt2 = 0;
	
	for(int i = st[R];i <= en[R];i++)
	{
		if(kk[i].ord >= l && kk[i].ord <= r)
		{
			kk[i].val += x;
			k[kk[i].ord] = kk[i];
			a[++cnt1] = kk[i];
		}
		else
		{
			b[++cnt2] = kk[i];
		} 
	}
	poi = st[R];
	while(i <= cnt1 && j <= cnt2)
	{
		node aa = a[i];
		node bb = b[j];
		if(aa.val > bb.val) 
		{
			kk[poi++] = bb;
			j++;
		}
		else 
		{
			kk[poi++] = aa;
			i++;
		}
	}
	while(i <= cnt1) kk[poi++] = a[i++];
	while(j <= cnt2) kk[poi++] = b[j++];

}

int find(int ro,int goa)
{
	int l = st[ro], r = en[ro] + 1;
	while(l < r)
	{
		int mid = (l + r + 1) >> 1;
		if(kk[mid].val <= goa) l = mid;
		else r = mid - 1;
	}
	return l;
}

int query(int l,int r,int x)
{
	int L = l >> K;
	int R = r >> K;
	if(L + 1 >= R)
	{
		vector <int> o;
		for(int i = l;i <= r;i++)
			o.push_back(k[i].val + seg[i>>K]);
		sort(o.begin(),o.end());
		return o[x-1];
	}
	int la = 1,ra = 1e7 + 1;
	int ans = 0;
	while(la <= ra)
	{
		int mid = (la + ra) / 2;
		int cnt = 0;
		for(int i = l;i <= en[L];i++)
		{
			if(k[i].val + seg[L] <= mid) cnt++; 
		}
		for(int i = st[R];i <= r;i++)
		{
			if(k[i].val + seg[R] <= mid) cnt++; 
		}
		for(int i = L + 1;i <= R - 1;i++)
		{
			if(kk[st[i]].val + seg[i] > mid) continue;
			if(kk[en[i]].val + seg[i] <= mid)
			{
				cnt += en[i] - st[i] + 1;
				continue;
			}
			int poi = find(i,mid - seg[i]);
			cnt += poi - st[i] + 1;
		}
		if(cnt < x) la = mid + 1 ; //  
		else
		{
			ra = mid - 1; // 
			ans = mid;
		} 
	}
	
	return ans;
}


int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		for(int i=n;i>=1;i--) st[i>>K] = i;
		for(int i=1;i<=n;i++) en[i>>K] = i,seg[i>>K] = 0;
		for(int i=1;i<=n;i++) 
		{
			cin>>k[i].val;
			k[i].ord = i;
			kk[i].val = k[i].val; 
			kk[i].ord = k[i].ord;
		}

		for(int i=0;i<=(n>>K);i++) sort(kk+st[i],kk+en[i]+1,cmp);
//		for(int i=1;i<=n;i++) cout<<kk[i].val<<" "<<kk[i].ord<<'\n';
		for(int i=1;i<=m;i++)
		{
			cin>>op>>ll>>rr>>s;
			if(op == 1)
			{
				add(ll,rr,s);
//				for(int j=1;j<=n;j++) cout<<k[j].val+seg[j>>K]<<' ';
//				cout<<'\n';
			}
			else 
			{
				cout<<query(ll,rr,s)<<"\n";
			}
		}
	}
	return 0;
}

  

3.子序列(涉及多维后缀和)

解法1:

解法2:分块 + 多维前缀和

这里g的初始化应该是对块前的所有数字计数,然后计算一下

解法3:

posted @ 2024-01-29 00:21  是谁不可理喻  阅读(29)  评论(0)    收藏  举报