7220. 【USACO 2021 US Open, Gold】United Cows of Farmer John

Description&Data Constraint

农夫约翰合牛国(The United Cows of Farmer John,UCFJ)将要选派一个代表队参加国际牛学奥林匹克(International bOvine olympIad,IOI)。

\(N\) 头奶牛参加了代表队选拔。她们站成一行,奶牛 \(i\) 的品种为 \(b_i\)

代表队将会由包含至少两头奶牛的连续区间组成——也就是说,对于满足 \(1\le l<r\le N\) 的奶牛 \(l\dots r\)。最边上的奶牛会被指定为领队。为了避免种内冲突,每一名领队都必须与代表队的其他成员(包括领队)品种不同。

请帮助 UCFJ 求出他们可以选派参加 IOI 的代表队的方法数。

\(1\le N \le 2\times 10^5\)

Solution

自然的想法是枚举每个位置作为左端点,往后扩展,更新答案。

其实可以考虑遍历每个位置,求出以这个位置为右端点的方案数。

首先处理出每个数字上次出现的位置 \(last_i\),那么对于右端点 \(i\),合法的左端点只能在 \(last_i+1\)\(i-1\) 中找。

当遍历到每个点时,我们令它的权值为 1,代表着这个点可以作为一种左端点的方案。

同时令它的上一个点的权值为 0,因为它无法再次成为左端点。

最后对于右端点 \(i\),它的贡献就是 \(last_i+1\)\(i-1\) 里 1 的个数。

单点修改,区间查询,随便搞个线段树或者树状数组即可。

时间复杂度 \(O(n\log n)\)?。

Code

#include<cstdio>
#define ll long long
#define N 200005
using namespace std;
int n,a[N],t[N],pre[N],tree[N<<2];
ll ans;
void modify(int now,int l,int r,int p,int v)
{
	if (l==p&&r==p)
	{
		tree[now]=v;
		return;
	}
	int mid=(l+r)>>1;
	if (p<=mid) modify(now<<1,l,mid,p,v);
	else modify(now<<1|1,mid+1,r,p,v);
	tree[now]=tree[now<<1]+tree[now<<1|1];
}
int query(int now,int l,int r,int p,int q)
{
	if (l>=p&&r<=q) return tree[now];
	int mid=(l+r)>>1;
	int res=0;
	if (p<=mid) res+=query(now<<1,l,mid,p,q);
	if (q>mid) res+=query(now<<1|1,mid+1,r,p,q);
	return res;
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	for (int i=1;i<=n;++i)
	{
		pre[i]=t[a[i]];
		t[a[i]]=i;
	}
	for (int i=1;i<=n;++i)
	{
		modify(1,0,n,pre[i],0);
		modify(1,0,n,i,1);
		ans+=(ll)query(1,0,n,pre[i]+1,i);
	}
	printf("%lld\n",(ll)ans-n);
	return 0;
}

7220. 【USACO 2021 US Open, Gold】United Cows of Farmer John

上一篇:使用快捷键提升C#开发效率


下一篇:知识总结