HDU 6964 I love counting

看到 \(\oplus\),肯定想到 trie。当我们一位可以是 \(1\) 但是变成 \(0\) 时,一个子树内的都合法。特殊的,最后走到的叶子也合法。

想法负一:我会莫队!时间复杂度 \(\mathcal{O}(n\sqrt{n}\log n)\)

想法零:我会树套树!这个做法第一步和想法一很类似,就是挂到右端点去。具体来说,对于每一个 bit 上的节点维护一个 trie,每一次更新最多添加 \(\mathcal{O}(\log^2 n)\) 个节点,因此时空均是 \(\mathcal{O}(\log^2 n)\) 的。

Code for 0
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const int N = 1e5+5;

int n,q,c[N],lst[N],ans[N];

struct node {
	int l,a,b,id;
};

vector<node> g[N];

const int C = N*18*18;

int rt[N],ch[C][2],tot,cnt[C];

void ins(int &p,int x,int v){
	if (!p) p=++tot;
	int cur=p;
	for (int i=17; i>=0; i--){
		int f=x>>i&1;
		if (!ch[cur][f]) ch[cur][f]=++tot;
		cur=ch[cur][f];
		cnt[cur]+=v;
	}
}

void M(int x,int y,int v){
	while (x<N){
		ins(rt[x],y,v),x+=x&-x;
	}
}

int S(int x,int a,int b){
	int res=0;
	for (int i=17; i>=0; i--){
		int f=a>>i&1;
		if (b>>i&1){
			res+=cnt[ch[x][f]];
			x=ch[x][f^1];
		}
		else x=ch[x][f];
	}
	return cnt[x]+res;
}

int Q(int x,int a,int b){
	int res=0;
	while (x){
		res+=S(rt[x],a,b),x-=x&-x;
	}
	return res;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	cin>>n;
	for (int i=1; i<=n; i++){
		cin>>c[i];
	}
	cin>>q;
	for (int i=1; i<=q; i++){
		int l,r,a,b;
		cin>>l>>r>>a>>b;
		g[r].push_back({l,a,b,i});
	}
	for (int i=1; i<=n; i++){
		if (lst[c[i]]){
			M(lst[c[i]],c[i],-1);
		}
		M(i,c[i],1);
		lst[c[i]]=i;
		for (auto u : g[i]){
			ans[u.id]=Q(i,u.a,u.b)-Q(u.l-1,u.a,u.b);
		}
	}
	for (int i=1; i<=q; i++){
		cout<<ans[i]<<"\n";
	}
	return 0;
}

想法一:我会 HH 的项链!我们按照 \(r\) 离线,每一次查询变成了找到合法的上一次出现 \(\ge l\) 的个数。按照 trie 上的 dfn 序建成一个线段树(每个节点维护上一次出现的位置),因为 trie 两个不互为祖先的子树不相交,问题变成了:一个询问做多有 \(\log n\) 个区间,问区间内大于等于一个数的个数。可以二分+主席树做到 \(\mathcal{O}(n\log^3 n)\),或者主席树上二分做到 \(\mathcal{O}(n\log^2 n)\)

这东西常数又大又难写,能不能再给力一点?

想法二:HH 的项链记录的贡献是最后一个出现的位置!那么我们一个数对 \((c,pos)\)(这个数,这个数出现的位置)在 \([L,R]\) 贡献到不仅要 \(c\) 本生合法,要 \(L\le pos\le R<nxt\)\(nxt\) 是下一个的位置。那么如果没有 trie 的限制(HH 的项链),就是一个普通的二维数点问题,数 \(L\le l\le R<r\) 的个数,右端点排序,相同先排询问来解决。其实有了 trie 的限制也是简单的。现在已经知道总共有 \(\mathcal{O}(n\log n)\) 个子树会有贡献,子树叶子数量之和是 \(\mathcal{O}(n\log n)\) 的(因为我们是 trie),那么我们可以离线下来,对于每一个子树算出会对哪些询问产生贡献,子树内做一个二维数点即可。这个是均摊 \(\mathcal{O}(n\log n)\) 的。

相较于想法零的有点:理论空间复杂度更优,但是实测差不多。

Code for 2
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const int N = 1e5+5;

int n,q,cnt=1,c[N],nxt[N],ch[N*18][2],ans[N],L[N],R[N];
vector<pair<int,int> > g[N*18];
vector<int> as[N*18];

void ins(int x,int p1,int p2){
	int cur=1;
	g[1].push_back({p1,p2});
	for (int i=17; i>=0; i--){
		int f=(x>>i&1);
		if (!ch[cur][f]) ch[cur][f]=++cnt;
		cur=ch[cur][f];
		g[cur].push_back({p1,p2});
	}
}

void qy(int id,int a,int b){
	int cur=1;
	for (int i=17; i>=0; i--){
		int f1=(a>>i&1),f2=(b>>i&1);
		if (f2==0){
			cur=ch[cur][f1];
		}
		else{
			as[ch[cur][f1]].push_back(id);
			cur=ch[cur][f1^1];
		}
	}
	as[cur].push_back(id);
}

struct node {
	int l,r,id;
	bool operator < (const node &x) const {
		return r!=x.r?r>x.r:id>x.id;
	}
} a[N*18];

int top,bit[N];

void M(int p,int v){
	while (p<N){
		bit[p]+=v,p+=p&-p;
	}
}

int Q(int p){
	int res=0;
	while (p){
		res+=bit[p],p-=p&-p;
	}
	return res;
}

void sol(int id){
	top=0;
	for (auto u : g[id]) a[++top]={u.first,u.second,0};
	for (auto u : as[id]) a[++top]={L[u],R[u],u};
	sort(a+1,a+1+top);
	for (int i=1; i<=top; i++){
		if (!a[i].id) M(a[i].l,1);
		else ans[a[i].id]+=Q(a[i].r)-Q(a[i].l-1);
	}
	for (int i=1; i<=top; i++){
		if (!a[i].id) M(a[i].l,-1);
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	cin>>n;
	for (int i=1; i<=n; i++){
		cin>>c[i];
		nxt[i]=n+1; 
	}
	for (int i=n; i; i--){
		ins(c[i],i,nxt[c[i]]);
		nxt[c[i]]=i;
	}
	cin>>q;
	for (int i=1; i<=q; i++){
		int a,b;
		cin>>L[i]>>R[i]>>a>>b;
		qy(i,a,b);
	}
	for (int i=1; i<=cnt; i++){
		sol(i);
	}
	for (int i=1; i<=q; i++){
		cout<<ans[i]<<"\n";
	}
	return 0;
}
posted @ 2024-11-11 22:37  SFlyer  阅读(25)  评论(0)    收藏  举报