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 1≤N≤3000
- 1 ≤ S ≤ 3000 1\leq S\leq 3000 1≤S≤3000
- 1 ≤ A i ≤ 3000 1\leq A_i\leq 3000 1≤Ai≤3000
输入
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} 2N−k,因此对于每个序列,其每多包含一个数字,包含这个序列的序列的可能性就会是原来的 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 j≥x,都有 d p [ j ] = d p [ j ] + d p [ j − x ] / 2 dp[j] = dp[j] + dp[j-x]/2 dp[j]=dp[j]+dp[j−x]/2,为什么后面的 d p [ j − x ] dp[j-x] dp[j−x]要除以二呢,原因就是你多加了一个数字 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} a∗2mod−2
但是这个题目里面,看厉害的大佬解答,都是用 ( 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∗(mod−mod/2),这个数学原理是什么不太了解,但是正确性可以简单的想一下,两个 a ∗ ( m o d − m o d / 2 ) a*(mod-mod/2) a∗(mod−mod/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;
}
博客讲述了AtCoder编号0026的Knapsack for All Subsets问题。通过动态规划解决满足Ai之和等于S的子集个数,初始dp[0]=2N。递推公式为dp[j]=dp[j]+dp[j-x] / 2 (模p运算),并讨论了模运算中除法的转换技巧。
585

被折叠的 条评论
为什么被折叠?



