Luogu-T157094 [RC-04] 子集积 (背包)

题面传送门

在这里插入图片描述

思路

  • 题目求大于 M 的个数,感觉乘积大于 M 的可以到很大,不好弄,可以考虑转化成求乘积小于等于 M 的子集个数。(用总子集个数 2^N 减去它就行了)
  • 考虑背包,令 f[x] 代表乘积等于 x 的子集个数,可以一个个元素往里加,每次更新一遍dp数组。
    最初始的暴力这个样子,无论空间、时间都是平方级别。
for (i=1; i<=N; i++)	//前 i 个元素
	for (j=1; j<=M; j++)	//乘积为 j
		f[i][j]=f[i-1][j]+f[i-1][j/a[i]];
  • 思考哪里可以优化,首先,每次需要修改的项其实挺少,可以考虑只算需要修改的 j ;其次,由于是多重集,可以考虑合并处理相同值的元素;再加上类似滚动数组的操作,就有了如下代码。

代码:

#include <cstdio>
#include <cstring>
#define Ha 998244353
#define Max 1000000
typedef long long LL;
using namespace std;

LL N,M,c[Max+5],f[Max+5],a[Max+5],b[Max+5],ans,ss;
LL jc[1000005];

LL ksm(LL x,LL n)
{
	LL ret=1;
	for (LL i=1,tmp=x; n; ) {
		if (n&i) {
			ret=ret*tmp%Ha;
			n^=i;
		}
		tmp=tmp*tmp%Ha;
		i<<=1;
	}
	return ret;
}

LL C(LL x,LL y)
{
	LL ret=jc[x];
	ret=ret*ksm(jc[y],Ha-2)%Ha;
	ret=ret*ksm(jc[x-y],Ha-2)%Ha;
	return ret;
}

int main()
{
	
	jc[0]=jc[1]=1;
	for (LL i=2; i<=1000000; i++) jc[i]=jc[i-1]*i%Ha;

	scanf("%lld%lld",&N,&M);
	for (LL i=1; i<=N; i++){
		scanf("%lld",&a[i]);
		c[a[i]]++;
	}
	
	f[1]=ksm(2,c[1]);
	for (LL i=2,cc,nn=N; i<=Max && nn; i++){	//元素是 i 的 
		for (LL k=i,tmp=1; k<=M && tmp<=c[i]; k=k*i,tmp++){		//选 tmp 个 i 
			cc=C(c[i],tmp);		//组合数 
			for (LL j=M/k; j; j--){
				b[j*k]=(b[j*k]+f[j]*cc)%Ha;		//dp 
			}
		}
		for (LL j=M/i; j; j--){	//清空临时改变量数组 
			f[j*i]=(f[j*i]+b[j*i])%Ha;
			b[j*i]=0;
		}
		nn-=c[i];
	}
	
	for (LL i=1; i<=M; i++)
		ss=(ss+f[i])%Ha;
	
	ans=ksm(2,N);
	ans-=ss;
	ans=(ans%Ha+Ha)%Ha;
	
	printf("%lld",ans);
	return 0;
}
/*
5 5
1 2 1 2 2
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值