此题是这道题的强化版
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));
}
好的思路是解决问题的一般,好的实现方式是解决问题的另一半
千万不要大力分类讨论(必要的分类讨论还是要有的)