洛谷P4135 作诗(不一样的分块)

题面

🔗

给定一个长度为 n n n 的整数序列 A A A ,序列中每个数在 [ 1 , c ] [1,c] [1,c] 范围内。有 m m m 次询问,每次询问查询一个区间 [ l , r ] [l,r] [l,r],问有多少个数在该区间中出现了偶数次,强制在线。

n , m , c ≤ 1 0 5 n,m,c\leq 10^5 n,m,c105.

题解

这道题的大方向是分块。

第一个做法是时间复杂度标准 O ( n n ) O(n\sqrt n) O(nn ) 的做法。

预处理分块数组

有很多种预处理方法,笔者就介绍一种吧(给后面的时间复杂度更优做法腾出空间)

我们令 f [ i ] [ j ] f[i][j] f[i][j] 为从块 i i i 开始到数列第 j j j 个元素的答案,令 c [ i ] [ j ] c[i][j] c[i][j] 为从块 i i i 开始,直到结束,数字 j j j 出现的次数。

这两者都很好预处理。然后询问的时候,先把 f [ b e l o n g [ l ] + 1 ] [ r ] f[belong[l]+1][r] f[belong[l]+1][r] 算进答案,再处理最左边的尾巴剩的那 O ( n ) O(\sqrt n) O(n ) 个数。每个数的出现次数可以通过 c [ b e l o n g [ l ] + 1 ] − c [ b e l o n g [ r ] ] c[belong[l]+1]-c[belong[r]] c[belong[l]+1]c[belong[r]] 再加上块 b e l o n g [ r ] belong[r] belong[r] 内枚举求出(后者得先处理,不然复杂度不对)。

很经典啊,但是我看了看评测记录,大多数人用这种分块跑了 4s +。

不够优啊。于是笔者想出了一种复杂度"更优"的做法。

数据结构分块讨论

设块大小为 B B B

出现次数大于 B B B 的数字不超过 n B \frac{n}{B} Bn 个,我们把这些数字出现次数的奇偶性状态全部用一个长度为 n B \frac{n}{B} Bnbitset 表示,然后直接做前缀异或,再随便(用可持久化线段树)维护任意区间出现数字的种类数。这部分贡献的时间复杂度 O ( n n 64 B ) O(n\frac{n}{64B}) O(n64Bn)

出现次数小于等于 B B B 的数,就用可持久化线段树暴力维护。维护任意区间的答案,每次把当前位置 R = i R=i R=i 前方与其相等的数都拿出来,令其位置为 b 1 , b 2 , . . . , b k b_1,b_2,...,b_k b1,b2,...,bk,然后该数在 L ∈ ( b k − 1 , b k ] L\in(b_{k-1},b_k] L(bk1,bk] 产生新贡献(这时 [ L , R ] [L,R] [L,R] 内刚好有两个数等于 a i a_i ai) ,在 L ∈ ( 0 , b 1 ] , ( b 1 , b 2 ] , . . . , ( b k − 2 , b k − 1 ] L\in(0,b_1],(b_1,b_2],...,(b_{k-2},b_{k-1}] L(0,b1],(b1,b2],...,(bk2,bk1] 的贡献分别取反。时间复杂度 O ( n log ⁡ n ⋅ B ) O(n\log n\cdot B) O(nlognB)

设置一个合理的 B B B ,可以使时间复杂度达到 O ( n n ⋅ log ⁡ n 64 ) O(n\sqrt{n\cdot \frac{\log n}{64}}) O(nn64logn ),常数相当,理论上更优

终于跑进了 4s 内(3.87 s)

CODE

数据结构分块讨论的代码:

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<regex>
using namespace std;
#define MAXN 100005
#define LL long long
#define ULL unsigned long long
#define UI unsigned int
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define eps (1e-4)
#define BI bitset<10001>
#define SQ 10
LL read() {
	LL f=1,x=0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f*x;
}
void putpos(LL x) {
	if(!x) return ;
	putpos(x/10); putchar('0'+(x%10));
}
void putnum(LL x) {
	if(!x) putchar('0');
	else if(x < 0) putchar('-'),putpos(-x);
	else putpos(x);
}
void AIput(LL x,char c) {putnum(x);putchar(c);}
int n,m,s,o,k;
BI sm[MAXN];
int pr[MAXN];
struct it{
	int ls,rs;
	int nm;
	it(){ls=rs=nm=0;}
}tr[MAXN*64];
int cnt,rt[MAXN];
int addtr(int a,int l,int r,int al,int ar,int y) {
	if(l > r || al > r || ar < l) return a;
	tr[++ cnt] = tr[a]; a = cnt;
	if(al >= l && ar <= r) {tr[a].nm += y;return a;}
	int md = (al + ar) >> 1;
	tr[a].ls = addtr(tr[a].ls,l,r,al,md,y);
	tr[a].rs = addtr(tr[a].rs,l,r,md+1,ar,y);
	return a;
}
int findtr(int a,int x,int al,int ar) {
	if(al > x || ar < x || !a) return 0;
	if(al == ar) return tr[a].nm;
	int md = (al + ar) >> 1;
	return tr[a].nm + findtr(tr[a].ls,x,al,md) + findtr(tr[a].rs,x,md+1,ar);
}
vector<pair<int,int>> tre[MAXN<<2];
int M;
void maketree(int n) {
	M = 1;while(M < n+2)M <<= 1;
	for(int i = 1;i <= M*2-1;i ++) {
		tre[i].clear(); tre[i].push_back(make_pair(0,0));
	}return ;
}
void ins(int s,int y,int tm) {
	int ls = tre[s].back().SE;
	if(tre[s].back().FI == tm) tre[s].pop_back();
	tre[s].push_back(make_pair(tm,y+ls));
}
void addtree(int l,int r,int y,int tm) {
	if(l > r) return ;
	int s = M+l-1,t = M+r+1;
	while(s || t) {
		if((s>>1) != (t>>1)) {
			if(!(s&1)) ins(s^1,y,tm);
			if(t & 1) ins(t^1,y,tm);
		}else break;
		s >>= 1;t >>= 1;
	}return ;
}
int findtree(int x,int tm) {
	int s = M+x,as = 0;
	while(s) as += tre[s][upper_bound(tre[s].begin(),tre[s].end(),make_pair(tm,0x7f7f7f7f))-tre[s].begin()-1].SE,s >>= 1;
	return as;
}
int ct[MAXN],a[MAXN];
int po[MAXN],cn;
vector<int> bu[MAXN];
int main() {
	n = read();m = read();int T = read();
	for(int i = 1;i <= n;i ++) {
		a[i] = read();
		ct[a[i]] ++;
	}
	for(int i = 1;i <= m;i ++) {
		if(ct[i] > SQ) {
			po[i] = ++ cn;
		}
	}
	rt[0] = 0;
	for(int i = 1;i <= n;i ++) {
		rt[i] = rt[i-1];
		if(po[a[i]]) {
			sm[i][po[a[i]]] = 1;
			rt[i] = addtr(rt[i],pr[a[i]] + 1,i,1,n,1);
			pr[a[i]] = i;
		}
		sm[i] ^= sm[i-1];
	}
	maketree(n);
	for(int i = 1;i <= n;i ++) {
		int x = a[i];
		if(!po[x]) {
			for(int j = (int)bu[x].size()-1,ct=1;j >= 0;j --,ct++) {
				int y = bu[x][j],pre = (j == 0 ? 0:bu[x][j-1]);
				if(ct & 1) addtree(pre+1,y,1,i);
				else addtree(pre+1,y,-1,i);
			}
			bu[x].push_back(i);
		}
	}
	int las = 0;
	while(T --) {
		s = read();o = read();
		s = (s + las) % n + 1;
		o = (o + las) % n + 1;
		if(s > o) swap(s,o);
		las = findtree(s,o);
		BI as = sm[o] ^ sm[s-1];
		las += findtr(rt[o],s,1,n) - (int)as.count();
		AIput(las,'\n');
	}
	return 0;
}

在这之后,我又打了一份第一个做法的代码,无优化交上去 2.90 秒 Rank#1

蚌埠住了

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<regex>
using namespace std;
#define MAXN 100005
#define LL long long
#define ULL unsigned long long
#define UI unsigned int
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define eps (1e-4)
#define SQ 320
LL read() {
	LL f=1,x=0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f*x;
}
void putpos(LL x) {
	if(!x) return ;
	putpos(x/10); putchar('0'+(x%10));
}
void putnum(LL x) {
	if(!x) putchar('0');
	else if(x < 0) putchar('-'),putpos(-x);
	else putpos(x);
}
void AIput(LL x,char c) {putnum(x);putchar(c);}
int n,m,s,o,k;
int a[MAXN],nx[MAXN],ps[MAXN];
int cn,bl[MAXN];
int f[SQ+2][MAXN],ct[MAXN];
int c[SQ+2][MAXN];
int main() {
	n = read();m = read();int T = read();
	for(int i = 1;i <= n;i ++) {
		a[i] = read();
		int B = i/SQ+1;
		if(!bl[B]) bl[B] = i;
		cn = B;
	}
	for(int i = 1;i <= m;i ++) ps[i] = n+1;
	for(int i = n;i > 0;i --) {
		nx[i] = ps[a[i]];
		ps[a[i]] = i;
	}
	for(int i = 1;i <= cn;i ++) {
		for(int j = bl[i];j <= n;j ++) {
			f[i][j] = f[i][j-1];
			int x = a[j];
			c[i][x] ++;
			if(c[i][x] > 1) {
				if(c[i][x] & 1) f[i][j] --;
				else f[i][j] ++;
			}
		}
	}
	int las = 0;
	while(T --) {
		s = read();o = read();
		s = (s + las) % n + 1;
		o = (o + las) % n + 1;
		if(s > o) swap(s,o);
		int ll = s/SQ+1,rr = o/SQ+1;
		las = 0;
		if(ll == rr) {
			for(int i = s;i <= o;i ++) {
				ct[a[i]] ++;
			}
			for(int i = s;i <= o;i ++) {
				if(ct[a[i]] > 0 && ct[a[i]]%2==0) las ++;
				ct[a[i]] = 0;
			}
		}
		else {
			las = f[ll+1][o];
			for(int i = s;i/SQ+1 == ll;i ++) {
				ct[a[i]] = c[ll+1][a[i]] - c[rr][a[i]];
			}
			for(int i = o;i/SQ+1 == rr;i --) {
				ct[a[i]] ++;
			}
			for(int i = bl[ll+1]-1;i >= s;i --) {
				int x = a[i];
				ct[x] ++;
				if(ct[x] > 1) {
					if(ct[x] & 1) las --;
					else las ++;
				}
			}
			for(int i = s;i < bl[ll+1];i ++) {
				ct[a[i]] = 0;
			}
			for(int i = o;i/SQ+1 == rr;i --) {
				ct[a[i]] = 0;
			}
		}
		AIput(las,'\n');
	}
	return 0;
}

于是我才发现,由于第二个做法处理 ≤ B \leq B B 的数时,用朴素的可持久化线段树空间开不下,于是我用了基于vector的可持久化数组,空间只用了 1/3 。缺点就是,查询的复杂度变成了 O ( log ⁡ 2 n ) O(\log^2n) O(log2n) ,成为复杂度瓶颈,于是总复杂度变成 O ( m log ⁡ 2 n ) O(m\log^2n) O(mlog2n)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这里是洛谷P3613寄包柜的代码,基于C++实现: ```c++ #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int INF = 0x3f3f3f3f; const int MAXN = 5005; int n, m, q; int f[MAXN], c[MAXN], h[MAXN], t[MAXN], st[MAXN]; int main() { scanf("%d%d%d", &n, &m, &q); for (int i = 1; i <= n; ++i) scanf("%d", &f[i]); for (int i = 1; i <= m; ++i) scanf("%d", &c[i]); for (int i = 1; i <= q; ++i) { int l, r, k; scanf("%d%d%d", &l, &r, &k); for (int j = l; j <= r; ++j) st[j - l] = f[j] - c[k]; sort(st, st + r - l + 1); int hh = 0, tt = -1; for (int j = l; j <= r; ++j) { while (hh <= tt && st[hh] + c[k] < h[j - 1]) ++hh; if (hh <= tt) t[j] = max(t[j], st[hh] + c[k]); while (hh <= tt && st[tt] >= st[j - l]) --tt; h[j] = max(h[j], st[j - l] + c[k]); st[++tt] = st[j - l]; } for (int j = l; j <= r; ++j) f[j] = max(f[j], t[j]); } int ans = -INF; for (int i = 1; i <= n; ++i) ans = max(ans, f[i]); printf("%d\n", ans); return 0; } ``` 思路: 题目要求的是一个区间内的最大值,可以考虑使用线段树或者分块数据结构,但是这道题的数据范围比较小,可以直接使用暴力。 考虑对于每次询问,对区间 $[l,r]$ 内的数值减去邮寄 $k$ 号包裹的费用,然后排序,设排序后的数组为 $st$,然后对于每个位置 $j$,维护两个值 $h_j$ 和 $t_j$,表示在 $j$ 位置之前,$st$ 中的最大值和次大值。这里可以使用单调队列维护次大值。然后对于每个位置 $j$,更新 $f_j$ 即可。最后输出所有位置的最大值即为答案。 时间复杂度:$O(nq\log n)$。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值