能够自己推出斜率优化的式子了...实属难得...
不过定义和实现都是参考了别人的博客的,╮(╯▽╰)╭...
woc...写博客的时候发现自己推的式子的变量有点问题.../难受
All submissions for this problem are available.### Read problem statements in Hindi, Bengali, Mandarin Chinese, Russian, and Vietnamese as well.
Nikki has N
coconuts, she wants to prepare a special coconut soup for her best friend Mansi. In order to make this soup, she has to break Z
coconuts. For each coconut, there is a fixed number of times Nikki needs to hit it if she wants it to break. Nikki can only hit one coconut at the same time.
Their friend Sakshi is a troublemaker. This time, Sakshi shuffled the coconuts in some (unknown) way. You are given a sequence A1,A2,…,AN
with the following meaning: it is possible to label the coconuts 1 through N in such a way that for each valid i, the i-th coconut needs to be hit exactly Ai
times to break.
Nikki wants to prepare the soup as soon as possible, so she wants to minimise the number of times she has to hit coconuts in the worst case in order to break Z
coconuts. Formally, she wants to find a strategy of hitting coconuts, possibly depending on which coconuts broke after which hits, such that no matter which coconuts broke and when, it is guaranteed that after H hits, there will be Z broken coconuts, and there is no strategy with smaller H. Help Nikki find H
— the minimum required number of hits.
For each test case, print a single line containing one integer — the minimum required number of hits.
Subtask #1 (10 points):
Subtask #2 (90 points): original constraints
2
2 1
50 55
2 1
40 100
55
80
Example case 1: Nikki can choose one coconut and try to hit it 55 times. It will break either after the 50-th hit or after the 55-th hit.
Example case 2: Nikki can choose one coconut and hit it 40 times. If it does not break, the other coconut must be the one that takes 40 hits to break, so she should hit the other coconut 40 times. In total, she needs to hit coconuts at most 80 times.
题目描述
Nikki有N个椰子。她想给她最好的朋友Mansi炖椰子汤。炖椰子汤需要打开Z个椰子。
每个椰子壳有不同的坚硬度,需要不同的敲击次数才能打开。Nikki 每次只能敲一个椰子。
他们有一个朋友Sakshi 是个捣蛋鬼。这一次 Sakshi 悄悄地随机打乱了椰子的顺序。
已知打乱前椰子依次需要敲A1, A2,A3....An下才能打开。被Sakshi打乱后没人知道每个椰子所对应的打乱前的位置,但可以确定的是椰子还是同样的N个椰子,椰子被移动不会影响打开它所需的敲打次数。
Nkki想尽快炖好汤,所以她想知道在最坏的情况下她至少需要敲几下才能打开Z个椰子。也就是说,她需要设计一个敲打椰子的策略,使得无论椰子被打乱成什么顺序,都能保证一共敲击H以后必然有至少Z个椰子被打开。并且,不可能找到-一个策略能够让H的值更小。Nikki的策略可以依据某一次敲击是否打开了某个椰子的情况来做决策。
请帮Nikki找到这个最小的H值。.
输入格式
输入数据第一行包含一个整数T,表示数据组数。接下来是T组数据。
每组数据第一行包含两个整数 N和Z。
接下来的一行包含N个整数A1,A2,A3...An,意义见题目描述。
输出格式
对于每组数据,输出一行包含一个整数,表示最坏情况下至少需要敲多少下。
有n个椰子,每个椰子有个“敲击次数”Ai,对于椰子 i 你要敲Ai次才能敲开此椰子
求敲开z个椰子所需要的最小敲击次数
参考博客&特别鸣谢:Hit the Coconuts
假设现在有n个数,若想要保证能取出一个椰子,至少需要敲 n*min{A1,A2,A3...An}次
即至少需要敲 “(数的个数)*(这些数里的最小值)”那么多次
那么把这些数从大到小排序(这样当前敲的i号椰子就是前i个椰子中的最小值),则:
dp[ i ][ j ]:前 i 个里面保证能取出 j 个椰子需要敲的次数
dp[ i ][ k ] = min( dp[ j ][ k − 1 ] + ( i − j ) × a[ i ] )
可用斜率优化解决。这里的 a[ i ] 是单调递减的。即斜率是单调递减的。
那么下凸壳维护的最优决策点是越来越靠左的,所以pop的是右端,即是一个单调栈。
我自己的【斜率优化式子的推导】,好像变量名有点问题,与代码的变量名对不上...但是本蒟蒻不知道哪里出了问题..._(:зゝ∠)_...
(一)推导斜率式子
设 ,其对应的dp值分别为:
若 j 的DP值比 l 的更优(更小),则:
移项化简得:
若满足上式,则 l 可以踢出队了(因为j更优嘛)
(二)再考虑如何维护并取得DP值(也就是答案)
刚刚上面说了, a[ i ] 是单调递减的,即斜率是单调递减的
那么下凸壳维护的最优决策点是越来越靠左的,所以pop的是右端
为什么呢?我可能太弱了,一开始没看懂,后来豁然开朗2333...这里我将整个过程想详细讲一讲:
首先,要从去掉“不够优秀”的点,及检查tail与tail-1的斜率是否满足>=a[ i ]
再解决新点的加入,从右往左来看,下凸壳的形状仍是上凸的(由不凹到凹)
而从左往右来看,有的点会与i形成下凹(由凹到不凹),所以要从队尾开始检查,删去不合法的点
例如下图,tail不合法,出队,然后又检查tail-1,以此类推
而在下图中,tail合法
所以(一)维护了下凸壳后,取对尾元素更新DP值,(二)再删去加入i后会不合法的点,最后将i入队,解决!
//刚刚那个版本居然暴力都没打对qwq...
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN=1000,INF=0x3f3f3f3f;
ll a[MAXN+5],dp[MAXN+5][MAXN+5];
//dp[i][j]:前i个椰子敲j个所需的最小花费
int n,z;
bool cmp(int a,int b)
{
return a>b;
}
void Solve()
{
sort(a+1,a+n+1,cmp);
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<=n;i++)
dp[i][1]=i*a[i];
for(int i=1;i<=n;i++)
for(int k=1;k<=i;k++)
for(int j=1;j
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN=1000,INF=0x3f3f3f3f;
const double eps=1e-10;
ll a[MAXN+5],que[MAXN+5],dp[MAXN+5][MAXN+5];
//dp[i][j]:前i个椰子敲j个所需的最小花费
int n,z;
bool cmp(int a,int b)
{
return a>b;
}
ll Y(int i,int cur)
{
return dp[i][cur];
}
ll K(int i,int j,int cur)
{
return (Y(i,cur)-Y(j,cur))/(i-j);
}
void Solve()
{
sort(a+1,a+n+1,cmp);
for(int i=0;i<=n;i++)
dp[i][1]=i*a[i];
for(int k=2;k<=z;k++)//式子中的"k-1"是相同的,则看成一个常数,循环做z次即可
{
int head=0,tail=0;
que[++head]=0;
que[++tail]=k-1;
for(int i=k;i<=n;i++)
{
while(head=a[i])
tail--;
int j=que[tail];
dp[i][k]=dp[j][k-1]+(i-j)*a[i];
while(head=K(que[tail],i,k-1))
tail--;
que[++tail]=i;
}
}
ll ans=INF;
for(int i=z;i<=n;i++)
ans=min(ans,dp[i][z]);
printf("%lld\n",ans);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&z);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
Solve();
}
return 0;
}