[BZOJ1150][CTSC2007]数据备份Backup(DP凸优化/wqs二分)

题目:

我是超链接

题解:

首先我们可以列出一个60pts的DP式
f [ 0 / 1 ] [ i ] [ j ] 表示i和i-1有没有相连,前i个分成j组的最小总长
那么转移很简单
f [ 0 ] [ i ] [ j ] = m i n ( f [ 0 ] [ i 1 ] [ j ] , f [ 1 ] [ i 1 ] [ j ] )
f [ 1 ] [ i ] [ j ] = f [ 0 ] [ i 1 ] [ j 1 ] + s [ i ] s [ i 1 ]
但是这样n^2肯定过不去
我们觉得很像省选一轮的D2T2
现在发现随着j的增加,这个函数的值一定是增的(废话),并且是下凸的,这也不难理解,因为我们一开始肯定是先选增值小的,然后选增加大的

考虑二分把枚举j的一维省略,我们按照套路,使每分一组获得一个代价来控制选的组数量
特别注意有切不到的情况,那么在<=k的时候我们都要更新答案

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#define LL long long 
using namespace std;
const int N=100005;
int s[N],n;LL mid;
struct hh{LL x,y;}f[2][N];
hh operator +(hh a,int b){return (hh){a.x+b,a.y};}
bool operator <(hh a,hh b){return a.x<b.x;}
hh add(hh a){return (hh){a.x-mid,a.y+1};}
void work()
{
    memset(f,0x7f,sizeof(f));
    f[0][1].x=0; f[0][1].y=0;
    for (int i=2;i<=n;i++)
    {
        f[0][i]=min(f[0][i-1],f[1][i-1]);
        f[1][i]=add(f[0][i-1]+(s[i]-s[i-1]));
    }
    f[0][n]=min(f[1][n],f[0][n]);
}
int main()
{
    int K;scanf("%d%d",&n,&K);
    LL l=0,r=0,ans=0;
    for (int i=1;i<=n;i++) scanf("%d",&s[i]),r+=(LL)s[i];
    while (l<=r)
    {
        mid=(l+r)>>1;
        work();
        if (f[0][n].y<=K) ans=f[0][n].x+(LL)K*mid,l=mid+1;else r=mid-1;//r=mid-1;else l=mid+1;
    }
    printf("%lld",ans);
}

猜你喜欢

转载自blog.csdn.net/blue_cuso4/article/details/80971696