CF1188C Array Beauty
题目大意
给出一个序列 a [ 1... n ] a[1...n] a[1...n] 还有一个 k k k
问a的所有长度为 k k k的子序列的价值和是多少
定义一个序列 b b b的价值为 m i n ( a b s ( b i − b j ) ) min(abs(b_i - b_j)) min(abs(bi−bj)) 即最接近的两个数的差
题解
首先因为是子序列,所有可以把a先排个序
然后发现 :
假设最大的数是 a [ n ] a[n] a[n],则对于这个序列价值 <= a [ n ] k − 1 \frac{a[n]}{k - 1} k−1a[n]
这个很容易证明吧,就不说了
然后可以很容易想到枚举价值,然后dp出当前价值的有多少种取法,然后乘一下再加起来
但是发现这个dp很难写(反正我不会)
考虑换一种dp方式,dp出大于当前价值的有多少种取法
然后把全部的加起来就行了
为什么呢?
假设当前枚举的价值为v,那么对于1…v 的dp出来的都有1的贡献,就相当于产生了v的贡献
然后加个前缀和优化就好了
看代码好了
code:
#include<bits/stdc++.h>
#define mod 998244353
using namespace std;
int n, k, a[1005], f[1005][1005];
int calc(int x){
int pre = 0; f[0][0] = 1;
for(int i = 1; i <= n; i ++){
while(a[pre] <= a[i] - x) pre ++;//前缀和优化
f[i][0] = 1;
for(int j = 1; j <= k; j ++){
f[i][j] = (f[i - 1][j] + f[pre - 1][j - 1]) % mod;//做前缀和,f[i-1][j]表示不选, 另一个表示选
}
}
return f[n][k];//返回大于价值x的长度为k的子序列个数
}
int main(){
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
sort(a + 1, a + 1 + n); a[0] = - mod;
int ans = 0;
for(int i = 1; (k - 1) * i <= a[n]; i ++) ans = (ans + calc(i)) % mod;//枚举价值并加起来
printf("%d", ans);
return 0;
}
坑点
貌似没有