数据结构专题

Codeforces Round 869 (Div. 1)

A

题目链接

Problem - A - Codeforces

题目大意

给定n个数,m次询问,每次询问一个区间,区间内不能有连续三个递减的数,即ai>=ai-1>=ai-2。求每次询问的l,r区间中可以选择的最大子序列大小。

数据范围

1≤n,q≤2e5

思路

这种区间查询的问题,并且是不带修改的区间查询,我们通常可以使用离线的算法,如莫队,ST表等数据结构来实现,这里采用莫队来做。

做法

这题可以说是莫队的板子了,除了莫队的基本操作(按左端点分块,右端点排序(奇偶优化))以外,需要修改的部分应该就是四个while循环了,也就是移动l,r指针的时候如果维护答案。可以发现,是找子序列最大个数,所以只需比较最后一段是否是连续递减的即可,举r指针右移为例子,如果是添加的数和最后两个数为递减关系,那么不对答案产生贡献,如果不是,则对答案产生贡献+1,剩下三种情况也容易推出来。

标签

莫队,数据结构

代码实现

struct pp {
	int l, r, idx, block;
	bool operator<(const pp&x)const{ 
		if (block != x.block) return block < x.block;
		else {
			if (block & 1) return r > x.r; //奇偶优化
			else return r < x.r;
		}
	}
}q[maxn];
int a[maxn];
void solve() {
	int n, m;
	cin >> n >> m;
	int len = sqrt(n); //块长
	vector<int>res(m + 1);
	for (int i = 1; i <= n; ++i) cin >> a[i];
	for (int i = 1; i <= m; ++i) { 
		cin >> q[i].l >> q[i].r;
		q[i].idx = i; q[i].block = (q[i].l - 1) / len + 1;//分块
	}
	sort(q + 1, q + 1 + m); //排序
	int l = 1, r = 0, cnt = 0;
	for (int i = 1; i <= m; ++i) {
		while (l < q[i].l) { //左指针右移
			--cnt;
			if (l + 2 > r) {
				++l;
				continue;
			}
			if (a[l] >= a[l + 1] && a[l + 1] >= a[l + 2]) ++cnt;
			++l;
		}
		while (l > q[i].l) { //左指针左移
			--l;
			++cnt;
			if (l + 2 > r) continue;
			if (a[l] >= a[l + 1] && a[l + 1] >= a[l + 2]) --cnt;  
		}
		while (r > q[i].r) {//右指针左移
			--cnt;
			if (l + 2 > r) {
				--r;
				continue;
			}
			if (a[r] <= a[r - 1] && a[r - 1] <= a[r - 2]) ++cnt;
			--r;
		}
		while (r < q[i].r) { //右指针右移
			++cnt;
			++r;
			if (l + 2 > r) continue;
			if (a[r] <= a[r - 1] && a[r - 1] <= a[r - 2]) --cnt;
		}
		res[q[i].idx] = cnt;
	}
	for (int i = 1;i<=m;++i) { //离线查询处理结果
		cout << res[i] << endl;
	}
}

时间复杂度

O(n+m*sqrt(n))

Rating

1500

小结

最近刚刚学的莫队,虽然这题的最优解不是莫队,但是好在莫队简单且好写,并且最近也在学习莫队,那么尝试用莫队写写,虽然自己的第一想法是dp(事实上这题确实dp才是最优解)。

Codeforces Round 849 (Div. 4) 

F

题目链接

Problem - F - Codeforces

题目大意

给定一个序列n和两种操作:

操作1,修改了l,r区间的序列,把区间内的数修改为它们的数位和。

操作2,查询下标为x的数

数据范围

1≤n,q≤2e5

思路

这题一眼就是要维护数据结构了。然而这题却异常简单,因为每个数的数位和最多进行两次累加后不会变化,因为最大为999999999=81(数位和)=9(数位和)=9=9...,所以修改只需要暴力的修改即可,我们需要维护的只有哪些下标不需要被修改,哪些下标需要被修改。这个可以用一个set来维护,然后我们把区间分成sqrt(n)个块,然后每个块都有一个set,set内存需要修改的数的下标,如果块内set为空,则表示没有需要修改的数。

做法

可以用分块来做,比较暴力,但能过。更好的方法有树状数组,线段树等,可以少一个根号的复杂度,但是这题给了2e5的数据范围,带根号的复杂度也能过,所以我们可以分块来做,对每个块进行暴力修改即可。如果不分块的话,需要遍历O(n),会超时。最后记得初始化!

标签

分块,数据结构

代码实现

int n,q,len;
int block[maxn],a[maxn];
set<int>st[500]; //块内大于等于10的数的下标 
int cal(int x){
	int s=0;
	while(x){
		s+=x%10;
		x/=10;
	}
	return s;
}
void init(){
	for(int i=1;i<=n;++i){
		block[i]=0;
		a[i]=0;
	}
	for(int i=1;i<=len+1;++i){
		st[i].clear();
	}
}
void update(int l,int r){
	for (int i = l; i <= min(r, (block[l] * len)); i++) { //左散块 
		if(a[i]<10) continue;
		a[i]=cal(a[i]);
		if(a[i]<10) st[block[i]].erase(i);
	}
	if (block[l] != block[r]) {
		for (int i = (block[r] - 1) * len + 1; i <= r; i++) {//右散块 
			if(a[i]<10) continue;
			a[i]=cal(a[i]);
			if(a[i]<10) st[block[i]].erase(i);
		}
	}
	for (int i = block[l] + 1; i <= block[r] - 1; i++) { //整块 
		if(st[i].empty()) continue;
		for(auto it=st[i].begin();it!=st[i].end();){
			a[*it]=cal(a[*it]);
			if(a[*it]<10) st[i].erase(it++); 
			else ++it;	
		}
	}
}
void solve(){
	cin>>n>>q;
	len=sqrt(n);
	init();
	for(int i=1;i<=n;++i){
		cin>>a[i];
		block[i]=(i-1)/len+1; 
		if(a[i]>=10){
			st[block[i]].insert(i);
		}
	}	 
	vector<int>res;
	while(q--){
		int op;
		cin>>op;
		if(op==1){//修改操作 
			int l,r;
			cin>>l>>r;
			update(l,r);
		}
		else{//查询操作 
			int x;
			cin>>x;
			cout<<a[x]<<endl;
		}
	}
}

时间复杂度

O(n+sqrt(n)*log2n*n)

Rating

1500

小结

线段树跑的很快,但我不会!分块才是暴力之神!

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值