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;
}

 

posted @ 2022-03-21 10:36  ggghj  阅读(71)  评论(0)    收藏  举报