题目大意
你有 n 个物品,第 i 个物品的重量是
w
i
w_i
wi。
你需要把这些物品划分成若干组,满足每一组的重量和都是质数。
两个方案是不同的当且仅当存在两个物品 i 和 j,在第一个方案里他们处在同一组,第二个方案里他们不处在同一组。
输入格式
第一行输入两个整数 n。
接下来一行 n 个整数
w
i
w_i
wi。
输出格式
输出一行一个整数表示方案数,答案可能很大,对998244353
取模后输出。
样例一
i
n
p
u
t
input
input
4
2 2 2 3
o
u
t
p
u
t
output
output
7
样例二
i
n
p
u
t
input
input
10
2 3 4 5 6 7 8 9 10 11
o
u
t
p
u
t
output
output
1177
限制与约定
1≤n≤16,2≤
w
i
w_i
wi≤100.
时间限制:1s
空间限制:512MB
题解
我们注意到,这里的n只有16的大小就直接进行二进制枚举,每次转移时直接找下一次所要划分的物品集合.我们还要预处理出对于一所选物品集合
S
S
S是否为质数,那我们就定义
S
u
b
f
[
s
]
Subf[s]
Subf[s]表示集合
s
s
s选取物品重量和是否为质数.
那么Dp转移方程式就是:
f
[
S
]
=
s
u
m
{
f
[
S
−
s
]
}
f[S]=sum\{ f[S-s]\}
f[S]=sum{f[S−s]}(
s
∈
S
s∈S
s∈S &&
S
u
b
f
[
s
]
=
t
r
u
e
Subf[s]=true
Subf[s]=true)
但是注意,我们只是这样写的话是会Wa的,因为我们会算重同一集合许多次,于是我们强制规定每次转移过来时必须选取位于最低位的数,这样就可以了。
代码
#include<set>
#include<map>
#include<ctime>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<climits>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
int read(){
int f=1,x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
return x*f;
}
#define MAXN 16
#define MAXL 1600
#define INF 0x3f3f3f3f
#define Mod 998244353
bool check[MAXL+5];
int tot,prime[80000+5];
void Prime(){
for(int i=2;i<=MAXL;i++){
if(!check[i]) prime[++tot]=i;
for(int j=1;j<=tot&&1ll*i*prime[j]<=MAXL;j++){
check[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
return ;
}
bool isPrime(int a){
for(int i=1;prime[i]*prime[i]<=a;i++)
if(a%prime[i]==0)
return 0;
return 1;
}
int n,w[MAXN+5],Subf[(1<<MAXN)+5],f[(1<<MAXN)+5];
int main(){//Subf[S]:集合为S的物品重量和是否为质数 f[S]:凑成物品S集合
Prime();
n=read();
for(int i=0;i<n;i++)
w[i]=read();
for(int S=1,tot=0;S<(1<<n);S++,tot=0){
for(int i=0;i<n;i++)//预处理
if(S>>i&1)
tot+=w[i];
if(isPrime(tot))
Subf[S]=1;
}
f[0]=1;
for(int S=1;S<(1<<n);S++){
int Low=S&-S;
for(int s=S;s;s=(s-1)&S)//枚举子集技巧
if((s&-s)==Low&&Subf[s])
f[S]=(f[S]+f[S-s])%Mod;
}
printf("%d\n",f[(1<<n)-1]);
return 0;
}