[ AtCoder Regular Contest 107 ] D - Number of Multisets(DP)
Problem
Solution
- 划分dp
记原问题选 \(n\) 个数之和为 \(k\),其中每个数是 \(\frac{1}{2^i},i \in [0,\infty]\) 的方案数为 \(f(n,k)\)。
求解划分dp一般抓住选 \(1\) 和不选 \(1\) 把原集合划分为两个集合。因此我们考虑:
-
选 \(1\) 这个集合可以表示为 \(f(n-1,k-1)\)。
-
不选 \(1\),那么这个问题即:\(n\) 个数之和为 \(K\),其中每个数是 \(\frac{1}{2^i},i \in [1,\infty]\) 的方案数。观察后发现,我们将该 \(n\) 个数全部乘 \(2\),那么等价于原问题限制:其中每个数是 \(\frac{1}{2^i},i \in [0,\infty]\)。所以该集合可以表示为 \(f(n,2k)\)
因此我们可以列出方程 \(f[n,k] = f[n-1,k-1] + f[n,2k]\)。边界 \(f[n,n] = 1, f[n,k]\)。边界 \(f[n,n] = 1, f[n,k] = 0(k>n)\)。
Code
Talk is cheap.Show me the code.
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
return x * f;
}
const int N = 3007, mod = 998244353;
int n,K;
int f[N][N];
int DP(int i,int k) {
if(k>i || k==0) return 0; //先要判断是否越界,如果 k>i 可以会爆数组 f[N][N] 的下表,因为 N=3007
if(f[i][k] != -1) return f[i][k];
if(i == k) return f[i][k] = 1;
return f[i][k] = (DP(i-1,k-1) + DP(i,2*k)) % mod;
}
signed main()
{
n = read(), K = read();
memset(f, -1, sizeof(f));
printf("%lld\n",DP(n,K));
return 0;
}
/*
2525 425
687232272
*/
Summary
- 划分dp抓住 选\(1\) 和不选 \(1\) 把原问题集合划分为两个集合。
Thanks
感谢 ovor大佬的题解 和 Atcoder官方题解 让我搞懂了这道题。