8VC Venture Cup 2016 - Elimination Round F - Group Projects dp好题
题目大意:给你n个物品, 每个物品有个权值ai, 把它们分成若干组, 总消耗为每组里的最大值减最小值之和。
问你一共有多少种分组方法。
思路:感觉刚看到的时候的想法是先排好序, dp[ i ][ j ][ k ]表示已经划分了 i 个, 并且分成了 j 组, 目前为止的总消耗为k的方案总数。
但是这个状态没有办法转移, 题解提供了一种将贡献分段的方法, 比如说原数组为1 2 3 6 7 8 10, 其中一组里面有 1 3 7 8 那么我们不是
直接计算出(8 - 1) 而是在dp的过程中计算 (2 - 1) + (3 - 2) + (6 - 3) + (7 - 6) + (8 - 7),每个数字累加上去的。
所以我们可能得到dp状态dp[ i ][ j ][ k ]表示已经用了前 i 个物品, 有 j 个组还能继续往里加数字, 总的消耗为k的方案数。
然后就能很容易写出dp方程啦。
#include<bits/stdc++.h> #define LL long long #define fi first #define se second #define mk make_pair #define PII pair<int, int> #define PLI pair<LL, int> #define ull unsigned long long using namespace std; const int N = 200 + 7; const int inf = 0x3f3f3f3f; const LL INF = 0x3f3f3f3f3f3f3f3f; const int mod = 1e9 + 7; const double eps = 1e-8; int n, m, cur, a[N], dp[2][207][1007]; void add(int &a, int b) { a += b; if(a >= mod) a -= mod; } int main() { scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) scanf("%d", &a[i]); sort(a + 1, a + 1 + n); dp[cur][0][0] = 1; for(int i = 1; i <= n; i++) { cur = cur ^ 1; memset(dp[cur], 0, sizeof(dp[cur])); for(int j = 0; j <= i; j++) { int cost = j*(a[i]-a[i-1]); for(int k = 0; k + cost <= m; k++) { add(dp[cur][j+1][k+cost], dp[cur^1][j][k]); add(dp[cur][j][k+cost], dp[cur^1][j][k]); add(dp[cur][j][k+cost], 1ll*j*dp[cur^1][j][k]%mod); if(j) add(dp[cur][j-1][k+cost], 1ll*j*dp[cur^1][j][k]%mod); } } } int ans = 0; for(int i = 0; i <= m; i++) add(ans, dp[cur][0][i]); printf("%d\n", ans); return 0; } /* */