P8810 [蓝桥杯 2022 国 C] 数组个数
wflengxuenong · · 个人记录
这是一个环形问题
方法一 dp
一般的环形问题是断环为链,扩展为2倍长度。这个问题回到尾部后还有看能否和断点连接。所以我们可以直接枚举起点。当然还是扩展一倍长度,直接用%n确定点的位置。
我们设
我们设
初始状态01位置还不能确定,从2位置开始确定1位置的。 对每个结点来说,如果前两个结点已经确定最大值,当前结点可以任意填数。 否则当前结点只能填确定的最大值。 结束后确定了n-2位置的,把环折回来再看n-1和0状态是否合法。最后仅把合法状态计入答案。
时间复杂度分析
因此总的时间复杂度为
#include<bits/stdc++.h>
using namespace std;
const int N=2029;
typedef long long ll;
const ll mod=1e9+7;
int n,a[N],b[N],c[N];//有几个连续的。
ll f[N][11][11],ans=0ll;//当前状态可以与前面两个位置的状态相关。
ll dp(int x,int y){
//01起点确定,从2开始,再回到01判断。
memset(f,0,sizeof(f));
f[1][x][y]=1;
for(int i=2;i<n;i++){
for(int j=a[i];j>=0;j--)
for(int k=a[i-1];k>=0;k--){
for(int l=a[i-2];l>=0;l--){
if(max(l,k)==b[i-1])f[i][k][j]=(f[i][k][j]+f[i-1][l][k])%mod;
else if(j==b[i-1])f[i][k][j]=(f[i][k][j]+f[i-1][l][k])%mod;
else break;
}
}
}
//判断 n-2 n-1,x y 决定的n-1,0
//
ll ans=0;
for(int i=a[n-1];i>=0;i--)
for(int j=a[n-2];j>=0;j--){
if(max(max(i,j),x)==b[n-1]&&max(max(i,x),y)==b[0]){
ans=(ans+f[n-1][j][i])%mod;
}
}
return ans ;
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
cin>>b[i];
b[i+n]=b[i];
}
for(int i=0;i<n;i++){
a[i]=min(min(b[(i+n-1)%n],b[i]),b[(i+1+n)%n]),a[i+n]=a[n];
}
ans=0;
for(int i=a[0];i>=0;i--){
for(int j=a[1];j>=0;j--){
ll tmp=dp(i,j);
ans=(ans+tmp)%mod;
}
}
cout<<ans<<endl;
}
方法二
根据b数值的情况,手工做几组样例我们可以发现:
当
如果
如果
我们设
现在
-
len<=2$,相邻数填了,方案数为$(a_i+1)^{len} -
len>=3$,那每3个数至少一个填$a_i 统计出连续长度后,我们可以设
f_{i0/1} 代表当前填数是否为t=a_i ,然后用动态规划转移。
f[i][1]=(f[i-1][0]+f[i-1][1]);
f[i][0]=(f[i-1][1]t+f[i-2][1]t*t);
特殊情况,所有的b_i 都相等
这时,就在一个环形区域内填数,每3个数至少有一个
复杂度分析
这个算法不依赖
#include<bits/stdc++.h>
using namespace std;
const int N=2029;
typedef long long ll;
const ll mod=1e9+7;
int n,a[N],b[N],c[N],rk[N];//有几个连续的。
ll f[N][2],ans=1ll;
ll work(int len,int t){//环的情况:连续长度未len,最大数为t,
if(len==1)return 1ll;
if(len==2)return ((t+1)*(t+1) )-1;
for(int i=0;i<=len;i++)f[i][0]=f[i][1]=0;
f[0][0]=t,f[0][1]=1;
f[1][0]=(t+1)*t;
f[1][1]=t+1;
for(int i=2;i<len;i++){
f[i][1]=(f[i-1][0]+f[i-1][1])%mod;
f[i][0]=(f[i-1][1]*t+f[i-2][1]*t*t)%mod;
}
return (f[len-1][0]+f[len-1][1])%mod;
}
ll work2(int len,int t){//线性情况:连续长度未len,最大数为t,
if(len==1)return ll(t+1);
if(len==2)return ((t+1)*(t+1) );
for(int i=0;i<=len;i++)f[i][0]=f[i][1]=0;
f[0][0]=t,f[0][1]=1;
f[1][0]=(t+1)*t;
f[1][1]=t+1;
for(int i=2;i<len;i++){
f[i][1]=(f[i-1][0]+f[i-1][1])%mod;
f[i][0]=(f[i-1][1]*t+f[i-2][1]*t*t)%mod;
}
return (f[len-1][0]+f[len-1][1])%mod;
}
int main(){
cin>>n;
for(int i=0;i<n;i++)cin>>b[i],b[i+n]=b[i];
for(int i=0;i<n;i++){
a[i]=min(min(b[(i-1+n)%n],b[i]),b[(i+1)%n]);
a[i+n]=a[i];
}
memset(c,-1,sizeof(c));
int flag=-1;
for(int i=0;i<n;i++) { //先把可以确定的确定。
if(b[(i-1+n)%n]<b[i])c[(i+1)%n]=c[(i+1)%n+n]=b[i],flag=(i+1)%n;
else if((b[(i-1+n)%n]>b[i]))c[(i-2+n)%n]=c[(i-2+n)%n+n]=b[(i-1+n)%n],flag=(i-2+n)%n;
}
//找出连续的数据段。
if(flag==-1){//所有数据一样,构成环。
ans=work(n,a[0]);
if(n==4)ans=(ans-(ll)pow(a[11],3))%mod;
else if(n>4)ans= (ans-(ll)pow(a[11],4)-2*(ll)pow(a[11],3))%mod;
if(ans<0)ans+=mod;
cout<<ans<<endl;
return 0;
}
int len=0;
for(int i=flag;i<=n+flag;i++) {
if(c[i%n]>-1)len=0;
else {
len++;
if(c[(i+1)%n]>-1){
ll tmp=work2(len,a[i%n]);
ans=ans*tmp%mod;
}
}
}
cout<<ans<<endl;
}
/*
6
8 8 8 5 5 4
366
*/