题目链接
题意:你有一个1到n的排列,
p
1
,
p
2
,
p
3
,
p
4
,
.
.
.
.
,
p
n
p1,p2,p3,p4,....,pn
p1,p2,p3,p4,....,pn,对于所有的
i
(
1
<
=
i
<
=
n
−
1
)
i(1<=i<=n-1)
i(1<=i<=n−1),如果
p
i
pi
pi 和
p
(
i
+
1
)
p(i+1)
p(i+1) 中,有一个数是另一个数的两倍,那么会在这两个数之间画一个点,否则不会,现在你把所有数字都擦掉了,只剩下了这些点,请问有多少种1到n的排列满足条件。
输入描述:第一行一个正整数 n ( 2 < = n < = 15 ) n(2<=n<=15) n(2<=n<=15),接下来一行一个长度 n − 1 n-1 n−1的 01 串,其中第 i i i个字符为1表示第 i i i个数与第 i + 1 i+1 i+1个数之间有点,否则没有。
输出描述: 输出一个答案,由于答案可能很大,对109+7取模。
思路:状压dp,设 d [ i ] [ j ] [ p ] d[i][j][p] d[i][j][p]为长度为i,最后一个数字是j,状态为p的方案数。状态p的范围是1<<16,以二进制位的形式表示状态, 如果p的1<< i - 1位为1,代表i这个数已经出现过,为0代表没有出现过。那么 k 的取值范围为(1~(1<<n-1) - 1),那么答案就是 d [ n ] [ 1 到 n ] [ ( 1 < < n − 1 ) − 1 ] d[n][1 到n][(1<<n-1)-1] d[n][1到n][(1<<n−1)−1]的和。四遍for循环,第一遍for枚举排列的长度,第二遍for枚举当前最后一个数字,第三层for枚举下一个位置的数字,当且仅当现在的最后一个数字和下一个位置的数字不相同且满足题目意思的时候,才开始枚举状态,当且仅当j出现过且k没有出现过才开始转移。转移方程是: d [ i + 1 ] [ k ] [ p ∣ ( 1 < < k − 1 ) ] + = p [ i ] [ j ] [ k ] d[i+1][k][p|(1<<k-1)] += p[i][j][k] d[i+1][k][p∣(1<<k−1)]+=p[i][j][k]。
反思:通过这道题认识到了运算符的优先级有多么的重要了,1<<k-1,会先算k-1,然后左移,1&1<< k-1 = = 0,会先算1<<k-1==0,然后跟1取&。
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 16,mod = 1e9+7;
int d[maxn][maxn][1<<16];
char a[maxn];
int ok(int i,int j)
{
if((i*2==j)||(j*2==i))return 1;
else return 0;
}
int main()
{
int n;
cin>>n>>a+1;
for(int i=1;i<=n;i++) //设置初始状态
d[1][i][(1<<i-1)]=1;
for(int i=1;i<n;i++) //枚举长度
for(int j=1;j<=n;j++) //枚举当前最后一个位置的数字
for(int k=1;k<=n;k++) //枚举下一个位置的最后一个数字
{
if(j!=k && (a[i]-'0')==ok(j,k)) //当且仅当j!=k和满足题目意思是开始枚举状态
{
for(int p=0; p < (1<<n); p++) //枚举状态
{
if(((p&(1<<j-1))!=0)&&((p&(1<<k-1))==0)) //当j已经存在,k不存在时开始转移
d[i+1][k][p|(1<<k-1)] = (d[i+1][k][p|(1<<k-1)] + d[i][j][p])%mod;
}
}
}
long long ans = 0;
for(int i=1;i<=n;i++)
ans = (ans+d[n][i][(1<<n)-1])%mod;
printf("%lld\n",ans);
}