划分物品

题目大意

你有 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[Ss]}( s ∈ S s∈S sS && 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值