此题是这道题的强化版

b值有5个,n<=3000

 

 

 

题解

考试的时候用1.5h,写了一个O(n^3)史诗级的分类讨论。。。结果只有O(n^4)的分,现在都没有找出错。。。

代码3.4k:(我的应该还算短的了,有好几个5k+的。。。)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 502
int a[N],b[14],f[N][N][N],g[N],h[N][N],p[N][N];
int main()
{
	int n,i,j,k;
	scanf("%d",&n);
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=1;i<=5;i++)scanf("%d",&b[i]);
	for(k=2;k<=n;k++){
		for(i=1;i<k;i++)
			for(j=i+1;j<=k;j++)
				f[k][a[i]][a[j]]++;
		for(i=1;i<=n;i++)
			g[k]+=f[k][i][i];
		for(i=1;i<=n;i++)
			for(j=1;j<=n;j++)
				if(i!=j)h[k][i]+=f[k][i][j],p[k][j]+=f[k][i][j];
	}
	long long ans=0;
	for(i=3;i<=n-2;i++){
		for(j=i+1;j<=n-1;j++){
			for(k=j+1;k<=n;k++){
				if((a[i]==a[j])!=(b[3]==b[4]))continue;
				if((a[i]==a[k])!=(b[3]==b[5]))continue;
				if((a[j]==a[k])!=(b[4]==b[5]))continue;
				
				if(b[1]==b[2]){
					if(b[1]==b[3])ans+=f[i-1][a[i]][a[i]];
					else if(b[1]==b[4])ans+=f[i-1][a[j]][a[j]];
					else if(b[1]==b[5])ans+=f[i-1][a[k]][a[k]];
					else{
						ans+=g[i-1]-f[i-1][a[i]][a[i]];
						if(b[4]!=b[3])ans-=f[i-1][a[j]][a[j]];
						if(b[5]!=b[4]&&b[5]!=b[3])ans-=f[i-1][a[k]][a[k]];
					}
				}
				else{
					if(b[1]==b[3]){
						if(b[2]==b[4])ans+=f[i-1][a[i]][a[j]];
						else if(b[2]==b[5])ans+=f[i-1][a[i]][a[k]];
						else{
							ans+=h[i-1][a[i]];
							if(b[4]!=b[3])ans-=f[i-1][a[i]][a[j]];
							if(b[5]!=b[4]&&b[5]!=b[3])ans-=f[i-1][a[i]][a[k]];
						}
					}
					else if(b[1]==b[4]){
						if(b[2]==b[3])ans+=f[i-1][a[j]][a[i]];
						else if(b[2]==b[5])ans+=f[i-1][a[j]][a[k]];
						else{
							ans+=h[i-1][a[j]];
							if(b[3]!=b[4])ans-=f[i-1][a[j]][a[i]];
							if(b[5]!=b[4]&&b[5]!=b[3])ans-=f[i-1][a[j]][a[k]];
						}
					}
					else if(b[1]==b[5]){
						if(b[2]==b[3])ans+=f[i-1][a[k]][a[i]];
						else if(b[2]=b[4])ans+=f[i-1][a[k]][a[j]];
						else{
							ans+=h[i-1][a[k]];
							if(b[3]!=b[5])ans-=f[i-1][a[k]][a[i]];
							if(b[4]!=b[3]&&b[4]!=b[5])ans-=f[i-1][a[k]][a[j]];
						}
					}
					else{
						if(b[2]==b[3]){
							ans+=p[i-1][a[i]];
							if(b[4]!=b[3])ans-=f[i-1][a[j]][a[i]];
							if(b[5]!=b[4]&&b[5]!=b[3])ans-=f[i-1][a[k]][a[i]];
						}
						else if(b[2]==b[4]){
							ans+=p[i-1][a[j]];
							if(b[3]!=b[4])ans-=f[i-1][a[i]][a[j]];
							if(b[5]!=b[3]&&b[5]!=b[4])ans-=f[i-1][a[k]][a[j]];
						}
						else if(b[2]==b[5]){
							ans+=p[i-1][a[k]];
							if(b[3]!=b[5])ans-=f[i-1][a[i]][a[k]];
							if(b[4]!=b[3]&&b[4]!=b[5])ans-=f[i-1][a[j]][a[k]];
						}
						else{
							ans+=(i-1)*(i-2)/2-g[i-1];
							if(b[3]==b[4]&&b[4]==b[5])
								ans-=h[i-1][a[i]]+p[i-1][a[i]];
							else if(b[3]==b[4]){
								ans-=h[i-1][a[i]]+h[i-1][a[j]];
								ans-=p[i-1][a[i]]+p[i-1][a[j]];
								ans+=f[i-1][a[i]][a[j]]+f[i-1][a[j]][a[i]];
							}
							else if(b[3]==b[5]){
								ans-=h[i-1][a[i]]+h[i-1][a[k]];
								ans-=p[i-1][a[i]]+p[i-1][a[k]];
								ans+=f[i-1][a[i]][a[k]]+f[i-1][a[k]][a[i]];
							}
							else if(b[4]==b[5]){
								ans-=h[i-1][a[j]]+h[i-1][a[k]];
								ans-=p[i-1][a[j]]+p[i-1][a[k]];
								ans+=f[i-1][a[j]][a[k]]+f[i-1][a[k]][a[i]];
							}
							else{
								ans-=h[i-1][a[i]]+h[i-1][a[j]]+h[i-1][a[k]];
								ans-=p[i-1][a[i]]+p[i-1][a[j]]+p[i-1][a[k]];
								ans+=f[i-1][a[i]][a[j]]+f[i-1][a[i]][a[k]];
								ans+=f[i-1][a[j]][a[i]]+f[i-1][a[j]][a[k]];
								ans+=f[i-1][a[k]][a[i]]+f[i-1][a[k]][a[j]];
							}
						}
					}
				}
			}
		}
	}
	printf("%lld",ans);
}

 

 

合理的分类与计算更能提高做这类题目的效率,切忌大力分类讨论,费力不讨好

如何合理的分类?

我们先会发现,b数组的重复元素比较多的时候,我们的计算更简便

我们可以尝试按出现2次以上的元素的种类来分类,那么只会有3类

 

首先来考虑有两个不同的重复2次以上的元素的情况,如1 3 4 1 3

我们可以利用DP的思想来进行计算

设 f[i] 表示现在已经匹配了 i 个元素的方案数

那么我们就需要用一个值来更新当前的 f 数组

接下来就是背包了,当前的数 x 如果匹配了b数组的1、2、4位,那么我们就要把f[1、2、4]更新的值更新到f[2、3、5](多匹配了一个)

怎么看是否匹配?直接看它是否等于那两个重复元素所对应的值(这两个值需要枚举)

但是我们发现这样做是O(n^3)的(枚举两个重复元素的对应值 i,j+扫一遍a来更新f数组)

怎么办?

我们发现剩下的最多只有一个元素,所以在扫描的时候,就乘上它可能出现的位置数就可以了,

于是我们只需要扫描枚举的 i,j 对应的位置来更新

先把每种值对应的下标预处理到一个vector里面

然后每次取出对应值的vector,进行归并,在归并的时候就可以更新 f 了

这样做是均摊O(n^2)的(应该挺好证明的吧)

代码:(注意最后还要再用剩下元素的位置数更新一下 f )

LL solve2(int x,int y)
{
	LL ans=0;
	for(int i=1;i<=n;i++)if(pos[i].size()){
		for(int j=1;j<=n;j++)if(pos[j].size()&&i!=j){
			LL f[6]={1};
			int pre,l,r,ml=pos[i].size(),mr=pos[j].size();
			pre=l=r=0;
			while(l<ml||r<mr){
				if(r==mr||(l<ml&&pos[i][l]<pos[j][r])){
					for(int k=5;k>=1;k--)if(b[k]!=x&&b[k]!=y)
						f[k]+=f[k-1]*(pos[i][l]-pre-1);
					for(int k=5;k>=1;k--)if(b[k]==x)
						f[k]+=f[k-1];
					pre=pos[i][l++];
				}
				else{
					for(int k=5;k>=1;k--)if(b[k]!=x&&b[k]!=y)
						f[k]+=f[k-1]*(pos[j][r]-pre-1);
					for(int k=5;k>=1;k--)if(b[k]==y)
						f[k]+=f[k-1];
					pre=pos[j][r++];
				}
			}
			for(int k=5;k>=1;k--)if(b[k]!=x&&b[k]!=y)
				f[k]+=f[k-1]*(n-pre);
			ans+=f[5];
		}
	}
	return ans;
}

 

 

再来考虑只有一种重复元素的情况

我们发现可以进行补集转换

先把一种元素的所有可能方案算出来(O(n^2),枚举一个对应值+扫一遍a来更新 f )

在减去剩下的元素有重复的情况(假设剩下了3个,就是枚举12,13,23,123四种相同的情况)

这是我们就可以直接利用solve2()来计算重复元素的情况了

代码:

LL solve1(int x)
{
	LL ans=0;
	for(int i=1;i<=n;i++)if(pos[i].size()){
		LL f[6]={1};
		for(int j=1;j<=n;j++){
			if(a[j]==i){
				for(int k=5;k>=1;k--)if(b[k]==x)
					f[k]+=f[k-1];
			}
			else{
				for(int k=5;k>=1;k--)if(b[k]!=x)
					f[k]+=f[k-1];
			}
		}
		ans+=f[5];
	}
	for(int i=1;i<=5;i++)if(b[i]!=x){
		for(int j=i+1;j<=5;j++)if(b[j]!=x){
			int t1=b[j];
			b[j]=b[i];
			ans-=solve2(x,b[i]);
			for(int k=j+1;k<=5;k++)if(b[k]!=x){
				int t2=b[k];
				b[k]=b[i];
				ans-=solve2(x,b[i]);
				b[k]=t2;
			}
			b[j]=t1;
		}
	}
	return ans;
}

 

最后来看所有元素互不相同的情况

其实这里就不需要补集转换了,因为直接按a值种类来做背包就可以了

代码:

LL solve0()
{
	LL f[6]={1};
	for(int i=1;i<=n;i++)if(pos[i].size())
		for(int k=5;k>=1;k--)
			f[k]+=f[k-1]*pos[i].size();
	return f[5];
}

 

完整代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define N 3005
#define LL long long
int a[N],b[6],cnt[6],n;
vector<int> pos[N];
LL solve2(int x,int y)
{
	LL ans=0;
	for(int i=1;i<=n;i++)if(pos[i].size()){
		for(int j=1;j<=n;j++)if(pos[j].size()&&i!=j){
			LL f[6]={1};
			int pre,l,r,ml=pos[i].size(),mr=pos[j].size();
			pre=l=r=0;
			while(l<ml||r<mr){
				if(r==mr||(l<ml&&pos[i][l]<pos[j][r])){
					for(int k=5;k>=1;k--)if(b[k]!=x&&b[k]!=y)
						f[k]+=f[k-1]*(pos[i][l]-pre-1);
					for(int k=5;k>=1;k--)if(b[k]==x)
						f[k]+=f[k-1];
					pre=pos[i][l++];
				}
				else{
					for(int k=5;k>=1;k--)if(b[k]!=x&&b[k]!=y)
						f[k]+=f[k-1]*(pos[j][r]-pre-1);
					for(int k=5;k>=1;k--)if(b[k]==y)
						f[k]+=f[k-1];
					pre=pos[j][r++];
				}
			}
			for(int k=5;k>=1;k--)if(b[k]!=x&&b[k]!=y)
				f[k]+=f[k-1]*(n-pre);
			ans+=f[5];
		}
	}
	return ans;
}
LL solve1(int x)
{
	LL ans=0;
	for(int i=1;i<=n;i++)if(pos[i].size()){
		LL f[6]={1};
		for(int j=1;j<=n;j++){
			if(a[j]==i){
				for(int k=5;k>=1;k--)if(b[k]==x)
					f[k]+=f[k-1];
			}
			else{
				for(int k=5;k>=1;k--)if(b[k]!=x)
					f[k]+=f[k-1];
			}
		}
		ans+=f[5];
	}
	for(int i=1;i<=5;i++)if(b[i]!=x){
		for(int j=i+1;j<=5;j++)if(b[j]!=x){
			int t1=b[j];
			b[j]=b[i];
			ans-=solve2(x,b[i]);
			for(int k=j+1;k<=5;k++)if(b[k]!=x){
				int t2=b[k];
				b[k]=b[i];
				ans-=solve2(x,b[i]);
				b[k]=t2;
			}
			b[j]=t1;
		}
	}
	return ans;
}
LL solve0()
{
	LL f[6]={1};
	for(int i=1;i<=n;i++)if(pos[i].size())
		for(int k=5;k>=1;k--)
			f[k]+=f[k-1]*pos[i].size();
	return f[5];
}
int main()
{
	int i,x=0,y=0,con=0;
	scanf("%d",&n);
	for(i=1;i<=n;i++)scanf("%d",&a[i]),pos[a[i]].push_back(i);
	for(i=1;i<=5;i++)scanf("%d",&b[i]),cnt[b[i]]++;
	for(i=1;i<=5;i++){
		if(cnt[i]>=2){
			con++;
			if(!x)x=i;
			else y=i;
		}
	}
	if(con==0)printf("%lld",solve0());
	if(con==1)printf("%lld",solve1(x));
	if(con==2)printf("%lld",solve2(x,y));
}

 

好的思路是解决问题的一般,好的实现方式是解决问题的另一半

千万不要大力分类讨论(必要的分类讨论还是要有的)