题解:P12236 [蓝桥杯 2023 国 Java A] 连续数组
guoshengyu1231 · · 题解
题目传送门
题意分析
通过读题,我们可以知道题目是要求我们构造出一个由数字
- 满足
a_{i+1}=a_i+1 。 - 满足
a_{i+1}\ne a_i+1 。
当接下来输入的
考虑到对于所有评测用例,满足
状态压缩简介
假设现在有一个集合,集合内有
既然提到了状态压缩 dp,那我就顺便讲一下状态压缩 dp 里一些常见的基本操作吧。
- 查询集合
S 的第i 位是否为1 (从0 开始):(S>>i)&1或S&(1<<i)。 - 将集合
S 的第i 位置0 (从0 开始):S^(1<<i)或(S>>i)^1。 - 将集合
S 的第i 位置1 (从0 开始):S|(1<<i)或(S>>i)|1。
其实有关状态压缩的二进制操作就这么些,关键是如何运用这些操作来解决问题。这些操作只是来帮助你解决问题的,所以关键还是具体思路。
具体思路
虽然是叫状态压缩 dp,但是他的本质依然是 dp。万变不离其宗,既然是 dp,那三要素可不能少。
状态
首先不难想到用一个二进制状态来表示选了哪些数字,但接下来我们还需要什么呢?当你实在想不到还需要什么时,你可能会认为应该没有了。但当你再一次读题时,你发现你还忽略了一个限制条件,那就是相邻两个数的限制。那这该怎么办呢?既然是相邻两个数,那必然有前一个数和后一个数,所以我们还得再设一个状态
边界
不难想到,当只有一个数的时候,此时肯定只要一种方案,所以可以很容易确定边界:
转移
接下来来到最难的一步:转移。我们知道,这题只限制相邻两个数,那我们已经知道了前一个数,只需要枚举后一个数就行了。
具体的,枚举
参考代码
#include<bits/stdc++.h>
#define int long long//不开long long见祖宗
using namespace std;
int n,a[20];
int dp[1<<20][20];
int pop_count(int x)
{
int cnt=0;
while(x)
{
if(x&1) cnt++;
x>>=1;
}
return cnt;
}//统计二进制数中1的数量
signed main()
{
cin>>n;
for(int i=1;i<n;i++) cin>>a[i]; //输入
for(int i=1;i<=n;i++) dp[1<<i-1][i]=1;//边界(初始化)
for(int s=1;s<(1<<n)-1;s++)
for(int i=1;i<=n;i++)//同题解中的last
if((s>>i-1)&1)//如果i属于s
{
int k=pop_count(s);//计算i是第几位
for(int j=1;j<=n;j++)//同题解中的next
if(!((s>>j-1)&1))//如果j不属于s
{
if(a[k]^(i+1==j)) continue;//如果不满足条件限制,则重开
int new_s=s|(1<<j-1);
dp[new_s][j]+=dp[s][i];//状态转移
}
}
int ans=0;
for(int i=1;i<=n;i++) ans+=dp[(1<<n)-1][i];//计算答案
cout<<ans;
return 0;
}