洛谷P4135 作诗 分块

https://www.luogu.org/problem/P4135

(不好意思我是吸了氧才过的,假如开O2会被卡两个点orz)

AcWing的地址:https://www.acwing.com/problem/content/description/265/

题意:

N个数,M组询问,每次询问需要你求出[l,r]中有多少个数出现正偶数次。

数据范围:

对于100%的数据,1<=n,c,m<=10^5

 

题解:

这是个强制在线的题目(疯狂暗示分块)

因为出现的数字的次数需要是偶数次,因此不同区间之间很难直接合并,对于这种区间不能直接合并的题目,可以参考分块求众数的方法

对于本题,因为不具备区间可加性

假如还是要预处理结果的话,我们需要一个数组res[i][j]表示从i区间到j区间出现正偶数次的数字的个数

对于块两边的那些不属于完整的块的地方,就用暴力的方法一个一个判断,因此我们还需要一个数组cnt[i][j],表示第i块以后(包括第i块),数字j出现的次数

设belong[i]表示i属于哪一块,blo_siz表示块的大小,那么对于l~r区间,我们需要暴力搜索的是[l , belong[l]*blo_siz] ,[(belong[r]-1)*blo_siz , r] ,对于每一个需要暴力搜索的值num,先统计它在需要暴力搜索的区间内出现的次数,然后使用cnt[belong[l]+1][num] - cnt[belong[r]][num] 求出它在块内出现的次数,就可以算出它是奇数还是偶数啦

 

下面粗略的算一下时间

n个数,设块的大小为x,那么在初始化阶段需要:

构建res数组(顺便也可以得到cnt数组): n/x*n

运行阶段:

设询问的规模等于n的大小,那么时间复杂度为: n*2*x ,看作 n*x (询问次数×需要暴力的个数)

加起来  n*(n/x+x), 当x=sqrt(n)的时候时间复杂度为 nsqrt(n), 可以过

(当然这道题也可以不使用cnt数组,而是用vector保存 每一个数出现的下标,然后使用二分的方法来实现cnt的功能,这个时候因为运行的时候多了个logn(二分),因此最优分块大小不为sqrt(n),而是 sqrt(n/logn) )

#include<bits/stdc++.h>
#define ll long long 
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e5 + 7;
const int maxn_blo = 320; 
//belong[i]: i属于哪一块
int belong[maxn], num[maxn];
//res:从i区间到j区间出现正偶数次的数字的个数 , cnt[i][j]:表示第i块以后(包括第i块),数字j出现的次数
int res[maxn_blo][maxn_blo], cnt[maxn_blo][maxn];
// blo_siz:一块的大小 blo_cnt: 分多少块
int n, c, m, blo_siz, blo_cnt;
//flag,sto都是工具人(数组)
int flag[maxn], sto[maxn];
void make_res(int beg,int cnt_flag) {
	//这里flag存的而是数字出现的次数
	//这里sto记录flag存了那些数字,减少无谓的遍历
	memset(flag, 0, sizeof(flag));
	int mx = 0, twice = 0, sto_index = 0;
	for (int i = beg; i <= blo_cnt; i++) {
		for (int j = (i - 1)*blo_siz + 1; j <= min(i * blo_siz, n); j++) {
			if (!flag[num[j]])
				sto[++sto_index] = num[j];
			flag[num[j]]++;
			//奇数并且大于1
			if ((flag[num[j]] & 1) && flag[num[j]] > 1)
				twice--;
			//偶数
			else if (!(flag[num[j]] & 1))
				twice++;
			if (cnt_flag) {//顺便求了个belong数组
				belong[j] = i;
			}
		}
		res[beg][i] = twice;
	}
	for (int i = 1; i <= sto_index; i++) {
		cnt[beg][sto[i]] = flag[sto[i]]; //后缀和
	}
}
void permake() {
	blo_siz = sqrt(n);
	blo_cnt = ((double)n) / blo_siz + 1;
	make_res(1, 1);
	for (int i = 2; i <= blo_cnt; i++)
		make_res(i, 0);
}
int solve(int l, int r) {
	int mx_cnt = 0, sto_index = 0, mx_num, ans = 0;
	int end;
	//这里因为没注意l,r在同一块的情况wa了2发
	if (belong[r] == belong[l]) end = r;
	else end = belong[l] * blo_siz;
	for (int i = l; i <= end; i++) {
		if (!flag[num[i]])
			sto[++sto_index] = num[i];
		flag[num[i]]++;
	}
	if (belong[l] != belong[r]) { //这里也是orz,,,
		for (int i = (belong[r] - 1)*blo_siz + 1; i <= r; i++) {
			if (!flag[num[i]])
				sto[++sto_index] = num[i];
			flag[num[i]] ++;
		}
	}
	if (belong[r] - belong[l] > 1)
		ans = res[belong[l] + 1][belong[r] - 1];
	int blo_cnt;
	for (int i = 1; i <= sto_index; i++) {
		blo_cnt = 0;
		if (belong[r] - belong[l] > 1)
			blo_cnt = cnt[belong[l]+1][sto[i]] - cnt[belong[r]][sto[i]];
		if (blo_cnt) {
			if (blo_cnt & 1 && flag[sto[i]] & 1)
				ans++;
			else if (blo_cnt % 2 == 0 && flag[sto[i]] & 1)
				ans--;
		}
		else {
			if (flag[sto[i]] % 2 == 0)
				ans++;
		}
		//记得reset
		flag[sto[i]] = 0;
	}
	return ans;
}
int main() {
	cin >> n >> c >> m;
	for (int i = 1; i <= n; i++)
		scanf("%d", num + i);
	permake();
	int ans = 0, l, r;
	memset(flag, 0, sizeof(flag));
	for (int i = 1; i <= m; i++) {
		scanf("%d %d", &l, &r);
		l = (l + ans) % n + 1, r = (r + ans) % n + 1;
		if (l > r)
			swap(l, r);
		ans = solve(l, r);
		printf("%d\n", ans);
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值