看了AgOH大佬的视频https://www.bilibili.com/video/BV1C4411u7rK?from=search&seid=5854182600235483127
然后写了视频里的洛谷板子题
洛谷P2709 小B的询问
https://www.luogu.com.cn/problem/P2709
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define ll long long
const int MAXN = 5e4+5;
int cnt[MAXN];//记录数字在区间[l,r]内出现的次数
int pos[MAXN],a[MAXN];
ll ans[MAXN];
int n,m,k,res;
struct Q{
int l,r,k;//k记录原来的编号
friend bool operator < (Q x,Q y){//同一个分块内r小的排前面;不同分块则按分块靠前的
return pos[x.l]==pos[y.l]?x.r<y.r:pos[x.l]<pos[y.l];
}
}q[MAXN];
void Add(int pos){
res -= cnt[a[pos]]*cnt[a[pos]];
cnt[a[pos]]++;
res += cnt[a[pos]]*cnt[a[pos]];
}
void Sub(int pos){
res -= cnt[a[pos]]*cnt[a[pos]];
cnt[a[pos]]--;
res += cnt[a[pos]]*cnt[a[pos]];
}
int main(){
cin>>n>>m>>k;//k为数字范围
memset(cnt,0,sizeof(cnt));
int siz = sqrt(k);//每个分块的大小
rep(i,1,n){
cin>>a[i];
pos[i] = i/siz;//分块
}
rep(i,1,m){
cin>>q[i].l>>q[i].r;
q[i].k = i;//记录原来的编号,用于打乱顺序后的还原
}
sort(q+1,q+1+m);
res = 0;//初始化res
int l = 1,r = 0;//当前知道的区间
//因为是闭区间,如果是[1,1]的话则一开始就包含一个元素了
rep(i,1,m){//莫队的核心,注意加减的顺序
while(q[i].l<l) Add(--l);
while(q[i].l>l) Sub(l++);
while(q[i].r<r) Sub(r--);
while(q[i].r>r) Add(++r);
ans[q[i].k] = res;
}
rep(i,1,m) cout<<ans[i]<<endl;
}
视频就讲了普通莫队,之后跟着博客园的帖子学:
https://www.cnblogs.com/WAMonster/p/10118934.html
然后学习了带修莫队
写的洛谷P1903 [国家集训队]数颜色 / 维护队列
https://www.luogu.com.cn/problem/P1903
这题参考的题解:https://www.luogu.com.cn/blog/Qiu/solution-p1903
发现这个人竟然才六年级呜呜,太恐怖了
发现带修莫队其实也是很暴力的一种算法。
与普通莫队的区别主要在这几点:
1.因为执行修改的时候需要修改的值不一定在区间的端点上,所以add和sub函数需要和普通莫队不一样,输入值从下标变成了数值。
2.引入时间戳概念
3.排序增加第三关键字,如果左右端点都处在同一个分块中,则根据修改顺序进行排序
4.分块的大小变成
n
2
3
n^\frac{2}{3}
n32
5.通过cq和cr两个变量来记录查询和修改的次数,同时读入查询的时候也通过cr的值确定当前cq的时间t
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
template<class T>inline void read(T &x){x=0;char o,f=1;while(o=getchar(),o<48)if(o==45)f=-f;do x=(x<<3)+(x<<1)+(o^48);while(o=getchar(),o>47);x*=f;}
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define ull unsigned long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define log(x) (31-__builtin_clz(x))
#define INF 0x3f3f3f3f
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff
int cnt[1000010] = {0};
const int MAXN = 2e5+5;
int a[MAXN],b[MAXN];//a读入一开始的序列,b记录修改后的
int pos[MAXN];//分块
int cq,cr;//统计查询修改次数
int R[MAXN][3];//0记位置,1记原本的值,2记修改后的值
ll res;
int ans[MAXN];//记录结果
int n,m;
void Add(int x){if(cnt[x]==0)res++;cnt[x]++;}//带修莫队的add和sub有区别
void Sub(int x){if(cnt[x]==1)res--;cnt[x]--;}
struct Q{
int l,r,k,t;
friend bool operator < (Q a,Q b){
return (pos[a.l]^pos[b.l])?pos[a.l]<pos[b.l]:((pos[a.r]^pos[b.r])?a.r<b.r:a.t<b.t);
//增加第三关键字,询问的先后顺序,用t或者k应该都行
}
}q[MAXN];
int main(){
cin>>n>>m;
cq = cr = 0;
int siz = pow(n,2.0/3.0);//这么分块最好,别问
rep(i,1,n){
cin>>a[i];
b[i]=a[i];
pos[i] = i/siz;
}
char hc;
rep(i,1,m){//读入修改和询问
cin>>hc;
if(hc=='Q'){
cin>>q[cq].l>>q[cq].r;
q[cq].k=cq;q[cq].t=cr;//注意这时候R[cr]还是没有的,这次询问是在R[cr-1]之后的
cq++;
}
else{
cin>>R[cr][0]>>R[cr][2];
R[cr][1] = b[R[cr][0]];
b[R[cr][0]] = R[cr][2];//在b数组中记录更改
cr++;
}
}
sort(q,q+cq);
int l=1,r=0,sjc=0;//时间戳
res = 0;
rep(i,0,cq-1){
while(sjc<q[i].t){
if(l<=R[sjc][0]&&R[sjc][0]<=r)//判断修改是否在该区间内
Sub(R[sjc][1]),Add(R[sjc][2]);
a[R[sjc][0]] = R[sjc][2];//在a上也进行更改
sjc++;
}
while(sjc>q[i].t){
sjc--;
if(l<=R[sjc][0]&&R[sjc][0]<=r)//判断修改是否在该区间内
Sub(R[sjc][2]),Add(R[sjc][1]);
a[R[sjc][0]] = R[sjc][1];//在a上也进行更改
}
while(l>q[i].l) Add(a[--l]);
while(l<q[i].l) Sub(a[l++]);
while(r<q[i].r) Add(a[++r]);
while(r>q[i].r) Sub(a[r--]);
ans[q[i].k] = res;
}
rep(i,0,cq-1) cout<<ans[i]<<endl;
}
//洛谷P1903 [国家集训队]数颜色 / 维护队列
//https://www.luogu.com.cn/problem/P1903