1259 “整数划分 V2” 动规
题面比较直白, 不解释
这个整数划分和dp的入门题的整数划分有不同,就是数据范围, 这个数据范围是5w, O(n^2)肯定不行.
这里的方法是我们想象: 把N的划分分为两个部分: 成分为**[1, 根号n], 另一部分是[(根号n) + 1, n]**.
比如n = 4: 4的开方为2; 那么我们对于{1, 3}, 这个划分, 就是由{1}, {3} 组成的,那么我们发现, 如果我们考虑小于根号n的这个部分, 我们可以设置状态dp[ n ] [根号n], 那么dp[i] [j]就表示为总和为i, 其中的最大的划分成分为j时候的可行的划分种数: 这个与入门题的整数划分思想一致,可以通过两次遍历求出这个数组中的所有值
那么对于第二部分:即每一个成分都大于根号n, 我们不难看出, 如果对于一个数i, 划分的结果中最小的成分不小于"(根号n)+1",那么每一个划分的组成元素的数量不会超过根号n, 想象总和为10000, 根号n就是100, 那么100个100就可以组成10000, 所以成分最多不会超过根号n个, 那么我们就可以把成分的数量设为dp的状态 dp1[ n ] [根号n] , 那么dp1[i] [j]表示总和为i, 由j个成分比根号n大的元素组成的划分的数量, 这个转移就可以考虑为: 假设当前dp1[sum] [j], 那么我要求最小的元素必须不小于"(根号n)+1", 所以 ①由dp1[sum- 根号n - 1] [j - 1]可以得到, 这个转移的原理就是我在dp1[sum- 根号n - 1] [j - 1]的所有划分上加一个单独的元素-------"(根号n)+1" ②由dp1[sum- j] [j]得到, 原理就是我在dp1[sum- j] [j]的所有划分上对于每一个元组的值都加上"1", 也是符合题意的.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e4 + 60;
const int mod = 1e9 + 7;
//全部由m以及它以下的成分组成
ll dp1[maxn];
//全部由m以上的成分组成
ll dp2[maxn][250];
ll sum[maxn];
void solve(){
int n;
cin>>n;
int m = sqrt(n);
dp1[0] = 1;
//m是允许的最大的值
for(int maxval = 1; maxval <= m; maxval++){
for(int tot = maxval;tot <=n;tot++){
dp1[tot] += dp1[tot - maxval];
dp1[tot] %= mod;
}
}
dp2[0][0] = 1;
//sum表示用大于(根号n)+1 的元素组成的总的方案数
sum[0] = 1;
//注意tot的起始值
for(int tot = m + 1; tot <= n;tot++){
for(int chose = 1;chose <= m;chose++){
dp2[tot][chose] = dp2[tot - (m + 1)][chose - 1] + dp2[tot - chose][chose];
dp2[tot][chose]%=mod;
sum[tot]+=dp2[tot][chose];
sum[tot]%=mod;
}
}
ll ans = 0;
for(int i = 0; i <= n; i++){
ans+=dp1[i] * sum[n-i];
ans%=mod;
}
cout<<ans<<endl;
}
int main() {
//freopen("in.txt", "r", stdin);
int T;
solve();
}

本文介绍一种解决整数划分问题的高效算法——整数划分V2。该算法通过将划分过程分为两部分:一部分使用小于等于根号n的数字进行划分,另一部分则使用大于根号n的数字进行划分,并利用动态规划来减少时间复杂度。
552

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



