CF626F Group Projects
题意
题目大意已经说得很清楚了
题解
神仙套路 DP?????
首先不考虑顺序的话就是直接把原数组从小到大排个序
然后考虑DP就相当于是括号匹配
好像有点抽
左括号的代价就是负的,然后右括号的代价就是正的
设
f
[
i
]
[
j
]
[
k
]
表
示
前
i
个
数
,
还
有
j
个
没
有
被
匹
配
,
代
价
为
k
的
方
案
数
设f[i][j][k]表示前i个数,还有j个没有被匹配,代价为k的方案数
设f[i][j][k]表示前i个数,还有j个没有被匹配,代价为k的方案数
转移十分简单
- f [ i + 1 ] [ j + 1 ] [ k − a [ i ] ] + = f [ i ] [ j ] [ k ] 作 为 左 括 号 ( 集 合 中 最 小 的 那 个 数 ) f[i+1][j+1][k -a[i]] +=f[i][j][k] 作为左括号(集合中最小的那个数) f[i+1][j+1][k−a[i]]+=f[i][j][k]作为左括号(集合中最小的那个数)
- f [ i + 1 ] [ j − 1 ] [ k + a [ i ] ] + = f [ i ] [ j ] [ k ] ∗ j 匹 配 掉 一 个 左 括 号 ( 作 为 集 合 中 最 大 的 那 个 数 ) f[i+1][j-1][k+a[i]]+=f[i][j][k]*j 匹配掉一个左括号(作为集合中最大的那个数) f[i+1][j−1][k+a[i]]+=f[i][j][k]∗j匹配掉一个左括号(作为集合中最大的那个数)
- f [ i + 1 ] [ j ] [ k ] + = f [ i ] [ j ] [ k ] ∗ ( j + 1 ) 作 为 前 面 的 一 个 未 被 匹 配 的 集 合 的 中 间 值 或 者 自 己 成 为 一 组 f[i+1][j][k] += f[i][j][k]*(j+1) 作为前面的一个未被匹配的集合的中间值或者自己成为一组 f[i+1][j][k]+=f[i][j][k]∗(j+1)作为前面的一个未被匹配的集合的中间值或者自己成为一组
答案就是
∑
f
[
n
]
[
0
]
[
0...
k
]
\sum f[n][0][0...k]
∑f[n][0][0...k]
然后可以发现这个转移是
O
(
1
)
O(1)
O(1)的,但是状态数是
O
(
n
2
∑
a
[
i
]
)
O(n^2 \sum a[i])
O(n2∑a[i])
发现直接GG了
通过题目可以发现 k < = 1000 k<=1000 k<=1000也就是说最后状态代价最多只有 0 ∼ 1000 0\sim1000 0∼1000是有用的,但是上面转移 k k k的范围是 − ∑ a [ i ] ∼ ∑ a [ i ] -\sum a[i] \sim \sum a[i] −∑a[i]∼∑a[i]会逝世
正解
考虑差分,那原来最大值减最小值就可以转换为差分数组的一段的和
那样就只有加没有减了
转
移
最
后
一
维
大
于
k
的
就
可
以
丢
掉
了
转移最后一维大于k的就可以丢掉了
转移最后一维大于k的就可以丢掉了
考虑转移
和上面的差不多,比较麻烦的就是代价的计算
就是当前差分乘上还没有被匹配的集合的个数
即
n
x
t
=
k
+
(
a
[
i
+
1
]
−
a
[
i
]
)
∗
j
nxt = k +(a[i+1] - a[i]) *j
nxt=k+(a[i+1]−a[i])∗j
然后转移就和账面一毛一个样了
code:
#include<bits/stdc++.h>
#define ll long long
#define N 505
#define mod 1000000007
using namespace std;
void Add(int &x, int y) {
x += y;
if(x >= mod) x-= mod;
}
int n, m, dp[2][N][N << 1], a[N];
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[0][0][0] = 1;
for(int i = 0; i < n; i ++) {
int fr = i & 1, to = fr ^ 1;
memset(dp[to], 0, sizeof dp[to]);
for(int j = 0; j <= n; j ++) {
for(int k = 0; k <= m; k ++) {
int nxt = k; Add(nxt, (ll)(a[i + 1] - a[i]) * j);
if(nxt > m || !dp[fr][j][k]) continue;//printf("%d %d %d %d %d %d\n", fr, to, j, k, nxt, i);
Add(dp[to][j + 1][nxt], dp[fr][j][k]); // zi li yi zu
if(j) Add(dp[to][j - 1][nxt], (ll)dp[fr][j][k] * j % mod);// xiao yi zu
Add(dp[to][j][nxt], (ll)dp[fr][j][k] * (j + 1) % mod); // bu xuan huo zuo wei zhong jian zhi
}
}
}
int ans = 0;
for(int i = 0; i <= m; i ++) Add(ans, dp[n & 1][0][i]);
printf("%d", ans);
return 0;
}
总结
我写DP像***
对于这种DP要把状态巧妙转化一下
转化的方法一般有
- 把贡献拆开
- 差分
- 看题解(呸