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: 写完啦