0026KnapsackForAllSubsets

博客讲述了AtCoder编号0026的Knapsack for All Subsets问题。通过动态规划解决满足Ai之和等于S的子集个数,初始dp[0]=2N。递推公式为dp[j]=dp[j]+dp[j-x] / 2 (模p运算),并讨论了模运算中除法的转换技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Knapsack for All Subsets

编号:0026

试题来源:AtCoder

试题描述

给定有 N N N个正整数的一个序列 A 1 , ⋯   , A N A_1,\cdots,A_N A1,,AN,以及一个正整数 S S S

对于任意一个 { 1 , 2 , ⋯   , N } \{1,2,\cdots,N\} {1,2,,N}的非空子集 T T T,定义 f ( T ) f(T) f(T)如下:

  • f ( T ) f(T) f(T)是互不相同的 T T T的非空子集 x 1 , x 2 , ⋯   , x k x_1,x_2,\cdots,x_k x1,x2,,xk满足 A x 1 + ⋯ + A x k = S A_{x_1}+\cdots+A_{x_k} = S Ax1++Axk=S

请找出 { 1 , 2 , ⋯   , N } \{1,2,\cdots,N\} {1,2,,N}的所有非空子集 T T T f ( T ) f(T) f(T)的和,该值模998244353为结果。

限制条件:

  • 所有的输入是整数
  • 1 ≤ N ≤ 3000 1\leq N\leq 3000 1N3000
  • 1 ≤ S ≤ 3000 1\leq S\leq 3000 1S3000
  • 1 ≤ A i ≤ 3000 1\leq A_i\leq 3000 1Ai3000

输入

N S

A 1 A_1 A1 ⋯ \cdots A N A_N AN

解答算法

首先,假定对于满足 A x 1 + A x 2 + ⋯ + A x k = S A_{x_1}+A_{x_2}+\cdots + A_{x_k}=S Ax1+Ax2++Axk=S的这样的子序列,那么显然,包含这个子序列的序列数目就为 2 N − k 2^{N-k} 2Nk,因此对于每个序列,其每多包含一个数字,包含这个序列的序列的可能性就会是原来的 1 2 \frac{1}{2} 21

因此我们可以设置一个 d p [ s ] dp[s] dp[s] d p [ s ] dp[s] dp[s]代表当前满足 A x 1 + A x 2 + ⋯ + A x k = S A_{x_1}+A_{x_2}+\cdots + A_{x_k}=S Ax1+Ax2++Axk=S的个数。

初始的时候有 d p [ 0 ] = 2 N dp[0]=2^N dp[0]=2N,这是显然的。

然后我们观察递推方程,假设当前读入了数字 x x x,那么显然对于任意的 j ≥ x j\geq x jx,都有 d p [ j ] = d p [ j ] + d p [ j − x ] / 2 dp[j] = dp[j] + dp[j-x]/2 dp[j]=dp[j]+dp[jx]/2,为什么后面的 d p [ j − x ] dp[j-x] dp[jx]要除以二呢,原因就是你多加了一个数字 x x x进入序列,那么其可能数目变为原来的 1 2 \frac{1}{2} 21

这个题目因为用到了求模,所以说在求模中的除法和正常不一样,要用到费马小定理,也就是把 ( a / 2 ) m o d p (a/2)mod\quad p (a/2)modp转化成 a ∗ 2 m o d − 2 a*2^{mod-2} a2mod2

但是这个题目里面,看厉害的大佬解答,都是 ( a / 2 ) m o d p (a/2)mod \quad p (a/2)modp转换成了 a ∗ ( m o d − m o d / 2 ) a*(mod-mod/2) a(modmod/2),这个数学原理是什么不太了解,但是正确性可以简单的想一下,两个 a ∗ ( m o d − m o d / 2 ) a*(mod-mod/2) a(modmod/2)相加,一定为 1 1 1,因此一个是原来的 1 2 \frac{1}{2} 21,如果有谁知道原理,希望指点我一下。

代码实现

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
typedef long long ll;
const int N=3005;
int n,s;
ll dp[N];
int main()
{
    ll inv2=mod-mod/2;   //定义的那个除以2的数
    dp[0]=1;           //初始dp[0]=1,和我说的等于2的N次方一个东西,因为他在最后,对结果乘以2的N次方。
    scanf("%d%d",&n,&s);
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        int x;scanf("%d",&x);
        for(int j=s;j>=x;j--)  //递推公式
            dp[j]=(dp[j]+dp[j-x]*inv2)%mod;
    }
    for(int i=1;i<=n;i++) dp[s]=dp[s]*2%mod;//对结果乘以2的N次方,和先对dp[0]进行这个操作是等价的
    printf("%lld\n",dp[s]);  //输出结果
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值