HH的项链 (莫队+树状数组+线段树) 代码练习
基础莫队算法
按照每个块排序 相当于优化了暴力
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=50010,M=2e5+10,S=1000010;
struct Node{
int id,l,r;
}q[M];
int w[N];
int cnt[S];
int ans[M];
int len,n,m;
int get(int x) {
return x/len;
}
bool cmp(const Node& a,const Node& b) {
int i=get(a.l),j=get(b.l);
if(i!=j) return i < j;
return a.r < b.r;
}
int res=0;
void add(int x) {
if(cnt[x]==0) {
res++;
}
cnt[x]++;
}
void del(int x){
cnt[x]--;
if(cnt[x]==0) {
res--;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1;i<=n;i++) {
cin >> w[i];
}
int m ;
cin >> m;
// 块的大小
len=max(1,(int)sqrt((double)n*n/m));
// len = max(1, (int)sqrt((double)n * n / m));
for(int i = 1;i<=m;i++) {
int l,r;
cin >> l >> r;
q[i] = {i,l,r};
}
sort(q+1,q+m+1,cmp);
for(int k=1,i=1,j=0;k<=m;k++) {
int id= q[k].id, l = q[k].l, r=q[k].r;
// cout << id << ' ' << l << ' ' << r << endl;
while(j<r) add(w[++j]);
while(j>r) del(w[j--]);
while(i<l) del(w[i++]);
while(i>l) add(w[--i]);
ans[id]=res;
}
for(int i=1;i<=m;i++) cout << ans[i] << endl;
return 0;
}
树状数组
按右端点从小到大排序 再离线处理
当右边添加到改元素时 必定包含该元素 之前出现的那个位置就可以删掉了
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=50010,M=2e5+10,S=1000010;
int tr[M];
int w[N];
int cnt[S];
int ans[M];
int len,n,m;
struct Node {
int id,l,r;
}q[M];
bool cmp(const Node& a,const Node& b) {
if(a.r != b.r) return a.r < b.r;
return a.l < b.l;
}
int res=0;
void add(int x,int k){
for(;x<=n;x+= -x&x) tr[x]+=k;
}
int ask(int x){
int res=0;
for(;x;x-= -x&x) res+=tr[x];
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1;i<=n;i++) {
cin >> w[i];
}
int m ;
cin >> m;
for(int i = 1;i<=m;i++) {
int l,r;
cin >> l >> r;
q[i] = {i,l,r};
}
sort(q+1,q+m+1,cmp);
int j = 1;
for(int i = 1;i<=m;i++) {
for(;j<=q[i].r;j++) {
if(cnt[w[j]]) add(cnt[w[j]],-1);
add(j,1);
cnt[w[j]]=j;
}
ans[q[i].id]=ask(q[i].r)-ask(q[i].l-1);
}
for(int i=1;i<=m;i++) cout << ans[i] << endl;
return 0;
}
线段树做法 和树状数组基本相同
维护一个区间的1的数量
若之前出现过数字 则以前的那个数字的下标设为0 更新当前下标
查询区间和(有多少个1)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=50010,M=2e5+10,S=1000010;
int w[N];
int cnt[S];
int ans[M];
int len,n,m;
struct node {
int l,r,val;
}tr[N*4];
struct Node {
int id,l,r;
}q[M];
bool cmp(const Node& a,const Node& b) {
if(a.r != b.r) return a.r < b.r;
return a.l < b.l;
}
int res=0;
void pushup(int u){
tr[u].val = tr[u<<1].val + tr[u<<1|1].val;
}
void build(int u,int l,int r) {
if(l==r) {
tr[u]={l,r,0};
}
else {
tr[u]={l,r};
int mid=l+r >> 1;
build(u << 1,l,mid);
build(u << 1|1,mid+1,r);
}
}
void modify(int u,int k,int d) {
if(tr[u].l == k && tr[u].r == k) {
tr[u].val +=d;
return;
}
if(k<=tr[u<<1].r) modify(u<<1,k,d);
else modify(u << 1|1,k,d);
pushup(u);
}
int query(int u,int l,int r) {
if(tr[u].l >=l&&tr[u].r <=r) {
return tr[u].val;
}
int sum=0;
if(tr[u<<1].r>=l) sum=query(u<<1,l,r);
if(tr[u<<1|1].l <=r) sum+=query(u<<1|1,l,r);
return sum;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1;i<=n;i++) {
cin >> w[i];
}
int m ;
cin >> m;
for(int i = 1;i<=m;i++) {
int l,r;
cin >> l >> r;
q[i] = {i,l,r};
}
sort(q+1,q+m+1,cmp);
int j = 1;
build(1,1,n);
for(int i = 1;i<=m;i++) {
for(;j<=q[i].r;j++) {
if(cnt[w[j]]) modify(1,cnt[w[j]],-1);
modify(1,j,1);
cnt[w[j]]=j;
}
ans[q[i].id]=query(1,q[i].l,q[i].r);
}
for(int i=1;i<=m;i++) cout << ans[i] << endl;
return 0;
}

浙公网安备 33010602011771号