划分物品

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37555704/article/details/82927566

题目大意

你有 n 个物品,第 i 个物品的重量是wiw_i
你需要把这些物品划分成若干组,满足每一组的重量和都是质数。
两个方案是不同的当且仅当存在两个物品 i 和 j,在第一个方案里他们处在同一组,第二个方案里他们不处在同一组。
输入格式
第一行输入两个整数 n。
接下来一行 n 个整数 wiw_i
输出格式
输出一行一个整数表示方案数,答案可能很大,对998244353取模后输出。
样例一
inputinput
4
2 2 2 3
outputoutput
7

样例二
inputinput
10
2 3 4 5 6 7 8 9 10 11
outputoutput
1177

限制与约定
1≤n≤16,2≤wiw_i≤100.
时间限制:1s
空间限制:512MB

题解

我们注意到,这里的n只有16的大小就直接进行二进制枚举,每次转移时直接找下一次所要划分的物品集合.我们还要预处理出对于一所选物品集合SS是否为质数,那我们就定义Subf[s]Subf[s]表示集合ss选取物品重量和是否为质数.
那么Dp转移方程式就是:
f[S]=sum{f[Ss]}f[S]=sum\{ f[S-s]\}(sSs∈S && Subf[s]=trueSubf[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;
}
展开阅读全文

没有更多推荐了,返回首页