c语言鸽巢原理,Codeforces 1188C DP 鸽巢原理

题意:定义一个序列的beauty值为序列中元素之差绝对值的最小值,现在给你一个数组,问所有长度为k的子序列的beauty值的和是多少?

思路:(官方题解)我们先解决这个问题的子问题:我们可以求出beauty值大于等于给你值的序列有多少个(假设为p[i]),那么其实答案就是∑(i从1到max(a)) p[i]。怎么求p数组呢?我们先对数组排序,假设现在求p[x], 设dp[i][j]为以第i个元素为结尾,长度为j的子序列的个数。那么所有a[i] - a[j] >= x的j都可以向i转移,所以,我们用指针维护满足a[i] - a[j] >= x的最靠近i的位置,并且维护前缀和以进行O(1)转移。这样每次的转移是O(n * k)的。假设m = max(a), 那么总的复杂度是O(m * n * k)。但是,我们发现,长度为k的序列有k - 1个差,那么beauty值的最小值为n / (k - 1) (由鸽巢原理可知), 所以复杂度变成了O(m / (k - 1) * n * k) = O(n * m)的,可以过。这个题需要注意一下,如果把dp的两维交换一下,即dp[i][j]表示长度为i的子序列中,以元素j为结尾的子序列的个数,这样表示可以快大概600ms。如果学过《深入理解计算机系统》可能会知道为什么,因为n >= k, 所以访问n的机会多,放第二维会让n的访问之间连续,时间更短。这题可能有点卡long long。

代码:

#include

#define LL long long

#define INF 0x3f3f3f3f

#define pii pair

#define db double

using namespace std;

const LL mod = 998244353;

const int maxn = 1010;

int dp[maxn][maxn];

int sum[maxn][maxn];

int a[maxn];

int ans = 0;

int n, k;

void solve(int x) {

for (int i = 0; i <= k; i++)

for (int j = 1; j <= n; j++) {

dp[i][j] = 0;

sum[i][j] = 0;

}

for (int i = 1; i <= n; i++) dp[1][i] = 1;

for (int i = 1; i <= k; i++) {

int l = 1;

for (int j = 1; j <= n; j++) {

while(a[j] - a[l] >= x) l++;

dp[i][j] = (dp[i][j] + sum[i - 1][l - 1]) % mod;

sum[i][j] = (dp[i][j] + sum[i][j - 1]) % mod;

}

}

for (int i = 1; i <= n; i++)

ans = (ans + dp[k][i]) % mod;

}

int main() {

scanf("%d%d", &n, &k);

for (int i = 1; i <= n; i++) {

scanf("%d", &a[i]);

}

sort(a + 1, a + 1 + n);

for (int i = 1; i <= a[n] / (k - 1); i++) {

solve(i);

}

printf("%d\n", ans);

}

标签:1188C,int,sum,Codeforces,long,maxn,DP,序列,dp

来源: https://www.cnblogs.com/pkgunboat/p/11144087.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值