P4065 [JXOI2017]颜色 线段树

题意:

戳这里

分析:

线段树的经典题 : 求点对的贡献

我们考虑怎么求这样的一个区间,我们可以枚举右端点 (r) ,然后找到一个合法的左端点 (l) ,满足([l,r]) 的颜色不在 ([1,l-1])([r+1,n]) 中出现过,然后我们发现影响一个颜色是否会被选的只有这个颜色的左右两个端点,即第一次出现的位置和最后一次出现的位置

现在假设我们枚举到了 (r) 我们如何找到一个合法的 (l) 呢?

首先我们可以求出 (l) 的左边界,因为 ([r+1,n]) 中的颜色都需要被删去,所以我们找到这些被删去的颜色中,在 (r) 以前最靠近 (r) 的位置记下来,记作 (x) 因为 (x) 必定不会和 (r) 相连所以我们不可能在 (x) 以左选 (l)

接着我们还要求,(l) 对应的颜色不能在 ([1,l-1]) 中出现过,也就是说 (l) 包含自己往右的所有颜色的左端点都在 ([l,r]) 之间,右端点不限制是因为上一步已经限制了,那些右端点在 (r) 之后的颜色都不会被考虑,对于这个限制,有一个小 (trick) 我们把左端点设为 (-1) 右端点设为 (1) ,求一下区间和值等于 (0) 的区间有多少个,区间和值不好求,但我们可以转化成前缀和,当 (sum_l==sum_i)([l,i]) 就是一个合法区间

也就是说我们需要查询区间内等于一个值的点有多少个,直接上主席树

代码:

#include<bits/stdc++.h>
#define pb push_back

using namespace std;

namespace zzc
{
	inline int read()
	{
		int x=0,f=1;char ch=getchar();
		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
		while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
		return x*f;
	}
	
	const int maxn = 3e5+5;
	int a[maxn],rt[maxn],pre[maxn],sum[maxn];
	int n,qt,tot;
	long long ans=0;
	bool vis[maxn];
	vector<int> col[maxn];
	set<int> s;
	typedef set<int>::iterator IT; 
	
	struct node
	{
		int lc,rc,siz;
		node(int lc=0,int rc=0,int siz=0):lc(lc),rc(rc),siz(siz){}
	}t[maxn<<5];
	
	void modify(int &now,int lst,int l,int r,int pos)
	{
		now=++tot;
		t[now]=t[lst];
		t[now].siz++;
		if(l==r) return ;
		int mid=(l+r)>>1;
		if(pos<=mid) modify(t[now].lc,t[lst].lc,l,mid,pos);
		else modify(t[now].rc,t[lst].rc,mid+1,r,pos);
	}
	
	int query(int now,int lst,int l,int r,int pos)
	{
		if(l==r) return t[now].siz-t[lst].siz;
		int mid=(l+r)>>1;
		if(pos<=mid) return query(t[now].lc,t[lst].lc,l,mid,pos);
		else return query(t[now].rc,t[lst].rc,mid+1,r,pos);
	}
	
	void work()
	{
		qt=read();
		while(qt--)
		{
			n=read();ans=0;
			for(int i=1;i<=n;i++) a[i]=read(),col[a[i]].pb(i);
			for(int i=n;i;i--)
			{
				IT it=s.end();
				if(s.size()) it--,pre[i]=(*it);
				else pre[i]=0;
				if(vis[a[i]]) s.erase(col[a[i]].back());
				else vis[a[i]]=true;
				col[a[i]].pop_back();
				if(col[a[i]].size()) s.insert(col[a[i]].back());  
			}
			for(int i=1;i<=n;i++) vis[i]=false;
			for(int i=1;i<=n;i++) if(!vis[a[i]]) sum[i]-=1,vis[a[i]]=true;
			for(int i=1;i<=n;i++) vis[i]=false;
			for(int i=n;i;i--) if(!vis[a[i]]) sum[i]+=1,vis[a[i]]=true;
			for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
			for(int i=0;i<=n;i++) sum[i]+=n;
			for(int i=1;i<=n;i++) modify(rt[i],rt[i-1],1,n,sum[i-1]);
			for(int i=1;i<=n;i++) if(pre[i]<i) ans+=query(rt[i],rt[pre[i]],1,n,sum[i]);
			printf("%lld
",ans);
			for(int i=0;i<=n;i++) col[i].clear(),vis[i]=false,sum[i]=0;
			for(int i=1;i<=tot;i++) t[i]=node();
			tot=0;s.clear();
		}
	
	}

}

int main()
{
	zzc::work();
	return 0;
}

原文地址:https://www.cnblogs.com/youth518/p/14291939.html