【思维】坚果
题目内容
小 \(\text{y}\) 喜欢吃坚果,这天他买来了 \(n\) 个坚果。每个坚果都需要若干次敲击才能打开。初始时,第 \(i\) 个坚果需要 \(a_i\) 次敲击。然而,他的朋友小 \(\text{c}\) 趁他不注意,打乱了这些坚果的顺序。小 \(\text{y}\) 非常郁闷,于是他想要吃 \(m\) 个坚果来平复心情,但是敲击坚果很费体力,所以他并不想过多敲击。小 \(\text{y}\) 想知道,在最坏情况下,至少需要敲击多少次坚果。小 \(\text{y}\) 并不会,于是他把这个任务交给了你。
数据范围
对于 \(50\%\) 的数据,满足 \(1\leq n\leq 4000\);
对于 \(100\%\) 的数据,满足 \(1\leq T\leq 10,1\leq m\leq n\leq 10000,n\times \max(a_i)\leq 10^9\)。
样例输入
2
2 1
50 55
2 1
40 100
样例输出
55
80
思路
首先将 \(a_i\) 从小到大排序,可以想到,在最坏的情况下,第 \(i+1\) 个坚果的敲击次数一定会大于等于第 \(i\) 个坚果的敲击次数。则之后一定会形成一个类似于高原形状的东西。
设 \(f[i][j]\) 表示后 \(i\) 个坚果中敲开了 \(j\) 个的最小敲击数,枚举下一个敲开的坚果 \(k\) ,可以得到 \(dp\) 转移方程:
移项后可以用斜率优化转移,时间复杂度 \(O(T\cdot n^2)\),可以获得 \(50\text{pts}\)。
当把 \(a[i]\) 的图画出来之后,可以看出其答案一定是下面黄色部分的面积。其中黄色到顶的就是敲开了。
其中竖着的一条直线是发现继续敲前面的坚果不如把之后的坚果敲完(如样例第一组数据)。可以证明一定有这样一条分界线,则答案一定是一个敲打完的前缀(\([1,i]\))和后面的一个区间(\([i+1,n]\))。由于总共要敲的 \(m\) 是确定的,所以枚举 \(i,j\) 时的 \(r\) 是可以算出的,利用前缀和可以求出 \(O(1)\) 计算面积的式子。这样的时间效率就是 \(O(T\cdot m(n-m))\) 的。
注意 \(n\times \max(a_i)\leq 10^9\),所以只需要开 int
数组就可以了。开 long long
数组会 \(\text{TLE}\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3f3f3f3f;
int n,m;
int a[maxn];
int sum[maxn];
int ans;
inline int read(){
int x=0;bool fopt=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
return fopt?x:-x;
}
int main(){
int T=read();
while(T--){
n=read();m=read();
ans=INF;
for(int i=1;i<=n;i++)
a[i]=read();
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+a[i];
for(register int i=0;i<=m;i++){
int len=m-i;
for(register int j=i+1;j+len-1<=n;j++){
int r=j+len-1;
int res=sum[i]+(j-i-1)*a[i]+sum[r]-sum[j-1]+a[r]*(n-r);
if(res<ans)ans=res;
}
}
printf("%d\n",ans);
}
return 0;
}