2024-03-08

2024-03-08

为了做寒假提单里面“历史研究”这道题复习莫队

P2709 小B的询问

简单的板子题
\(len=\frac{n}{\sqrt{m}}\)
根据完全平方公式
add 的时候 ans 加上 \(2\times cnt+1\) cnt再加一
del 的时候 ans 减去 \(2\times cnt-1\) cnt再减一

被一个意想不到的错误卡了很久:

调用del函数的时候,其中一个圆扩号()打成了方括号[]结果一直是一堆特别大的数(很奇怪,竟然能跑??)其 实他一直有一个warning但我没有仔细看……

以后要仔细检查(有warning要认真看)

歴史の研究

回滚莫队:解决删除难的问题(如 max 不知道次大值不好删除)

排序后分块处理,每次左右指针初始在块的右端
\(--\)如果当前处理到的询问左右端点在同一块直接 \(brute \ force\)
\(--\)不在同一块内的情况先扩展到右端点,然后用变量 tmp 记下当前的 ans ,再扩展到左端点得出当前询问的答案,然后把答案还原到 tmp ,左边再还原回去,右端点由于单调增不需要还原,这样就不用进行删除操作了

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>

using namespace std;

typedef long long ll;

const int N=1e5+10;
const int Inf=2e9;

int n,m;
int len,blk[N];
int lft[N],rgh[N];
int w[N];
ll cnt[N],ans,res[N];

vector<int> nums;

struct Query {
	int id;
	int l,r;
}Q[N];

bool cmpq(Query A,Query B) {
	if(blk[A.l]==blk[B.l]) return A.r<B.r;
	return blk[A.l]<blk[B.l];
}

int main() {
	scanf("%d%d",&n,&m);
	len=n/sqrt(m);
	for(int i=1;i<=n;i++) blk[i]=(i-1)/len+1,lft[blk[i]]=Inf,rgh[blk[i]]=-Inf;
	for(int i=1;i<=n;i++) lft[blk[i]]=min(lft[blk[i]],i),rgh[blk[i]]=max(rgh[blk[i]],i);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]),nums.push_back(w[i]);
	sort(nums.begin(),nums.end());
	nums.erase(unique(nums.begin(),nums.end()),nums.end());
	for(int i=1;i<=n;i++) w[i]=lower_bound(nums.begin(),nums.end(),w[i])-nums.begin();
	for(int i=1;i<=m;i++) scanf("%d%d",&Q[i].l,&Q[i].r),Q[i].id=i;
	sort(Q+1,Q+m+1,cmpq);
	for(int b=1,qid=1;b<=blk[n];b++) {
		ans=0;
		for(int i=0;i<=n;i++) cnt[i]=0;
		int p=rgh[b],q=p+1;
		while(qid<=m&&blk[Q[qid].l]==b) {
			if(blk[Q[qid].l]==blk[Q[qid].r]) {
				ll ret=0;
				for(int i=Q[qid].l;i<=Q[qid].r;i++) cnt[w[i]]++;
				for(int i=Q[qid].l;i<=Q[qid].r;i++) ret=max(ret,cnt[w[i]]*nums[w[i]]);
				for(int i=Q[qid].l;i<=Q[qid].r;i++) cnt[w[i]]--;
				res[Q[qid].id]=ret;
				qid++;
				continue;
			}
			while(p<Q[qid].r) {
				p++;
				cnt[w[p]]++;
				ans=max(ans,cnt[w[p]]*nums[w[p]]);
			}
			ll tmp=ans;
			while(q>Q[qid].l) {
				q--;
				cnt[w[q]]++;
				ans=max(ans,cnt[w[q]]*nums[w[q]]);
			}
			res[Q[qid].id]=ans;
			ans=tmp;
			while(q<=rgh[b]) cnt[w[q++]]--;
			qid++;
		}
	}
	for(int i=1;i<=m;i++) printf("%lld\n",res[i]);
	
	return 0;
}

离散化 lower_bound 的时候没有+1,离散化之后最小值是 \(0\) ,cnt数组清空的时候从零开始(下回记住清空在不影响复杂度的前提下多清空几个不影响答案,或者直接用 memset )

“十年 OI 一场空,不开 long long 见祖宗”

几句话

今天去任老师办公室喝茶了hhh(茉莉花茶,挺好喝的🤣),老师让我要“上劲儿”,多做题,练码力
确实啊 要努力啦
多思考,《具体数学》也得看看

这两天是竞赛课时间,但是总感觉两天没有多少成果
可能也有跑操、“出征大会”、体育课的影响👀
但是算了一下好像时间也不是很少,两天一共有15个小时的时间
还是效率不高啊
周日再加油吧💪💪

计划有变

今天晚自习不用回班了,晚三去搬个东西就行了
接着写题

神秘数

区间内不能用所有数组成的可重集合的子集和表示的最小的数

数从小到大考虑 假设当前 区间 \([1,x]\) 都可以拼出,现在又加入了一个数 \(y\)
分类讨论

  • \(y>x+1\)\(x+1\) 一定不能被拼出 答案为 \(x+1\)
  • \(y\le x+1\) 则可拼出的区间变为 \([1,x+y]\)

优化的方法是

\(ans\) 初始为 \(1\)
若区间内小于 \(ans\) 的数的和 \(sum\ge ans\) 说明一定存在 至少一个比 \(ans\) 小且没有被选上的数
那么区间就可以扩展到 \(ans=sum+1\)

用可持久化线段树来维护区间内小于 \(ans\) 的数的和

主席树空间 \(O(m\log n)\)

学了线段树合并,但是题还没时间做

update: 写完啦

posted @ 2024-03-08 17:12  OrangeStar*  阅读(9)  评论(0编辑  收藏  举报