P2396


状压好题。

题意

给你一个长度为 \(n\) 的正整数序列 \(a\),再有 \(k\) 个正整数。
求有多少种 \(a\) 的排列方式使得其前缀和不会成为那 \(k\) 个数里的任意一个。答案对 \(1e9+7\) 取模。
\(0 \le k \le 2,1 \le n \le 24\)

题解

因为 \(n \le 24\),明显是状压。

假设 \(f_i\) 表示在状态为 \(i\) 的情况下的组合方法数,则答案为 \(f_{2^n-1}\)

考虑如何转移。

对于 \(f_i\) 可以由 \(f_j\) 转移而来,其中 \(j\)\(i\) 去掉一个元素得来的。

那么状态转移方程就是 \(f_i = \sum f_j\),在循环内枚举每一位的情况即可。

因为要用到取最后一位的操作,所以要用到 \(lowbit\) 进行优化。

定义一个数组 \(a\),其中 \(a_i\) 存储在情况 \(i\) 下的元素之和,很容易理解。

对于一个新的 \(a_i\)\(a_i = a_{lowbit(i)} + a_{i \, xor \, lowbit(i)}\)

代码

亲测不开 O2 能过。

#include 
#define ll long long
#define lb(x) (x&(-x))
using namespace std;const ll N=24,mod=1e9+7;
int a[1<>n;lim=(1<>a[i];
    cin>>k;for(int i=1;i<=k;i++)cin>>b[i];f[0]=1;
    for(int i=0;i<=lim;i++){
	a[i]=a[i^lb(i)]+a[lb(i)];
	if(a[i]!=b[1]&&a[i]!=b[2])
	    for(int j=i;j;j^=lb(j))
                f[i]=(f[i^lb(j)]+f[i])%mod;
    }cout<

还似乎有双倍经验。