【JZOJ 4984】 太空飞船

28 篇文章 0 订阅
17 篇文章 0 订阅

Description

这里写图片描述
这里写图片描述

Analysis

这个数据范围很显然的要分段程序

K=2

两段越平均越好,直接O(n)扫一遍即可

K=3

三段越平均越好,我们枚举其中一个等分点,调整另一个等分点(这个可以通过指针移动或二分来实现)。然后我们就有了两个等分点。但是这两个等分点的答案不一定是最优的。所以左边的等分点可能向右移一格。这会造成什么后果呢?右边的等分点会向右移动若干格。这个可以二分搞一波,然后整个过程就是O(nlogn)的
(我没打二分直接调整两个等分点向左右偏离一点点取min水过去了,面壁中)

K>3且N<=400

这个数据范围可以dp啦><
枚举圆环在哪个位置断开
状态很好设,f[i][j]表示前i个位置分了j段的最小值。
写出DP式子,就是

f[i][j]=min(f[k][j1]+(s[i]s[k]avg)2)(k<i)

s表示前缀和
O(n3k) ,爆炸
一看,嘿嘿嘿,这不是斜率优化标准形式吗,化简一下式子,两个决策k,l,k优于l当且仅当
g(k,l)=F(k,j)F(l,j)s[k]s[l]<2s[i]

其中定义 F(i,j)=f[k][j1]+s[k]2+2avgs[k]
i递增
然后就是裸的斜率优化了, O(n2k)

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(ll i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const ll N=600005,M=22;
const ll INF=9187201950435737471;
ll n,k,avg,tot,a[N],b[N],f[405][M],s[N],q[N],S[N];
ll sqr(ll x){return x*x;}
ll F(int i,int j)
{
    return f[i][j]+sqr(s[i])+2*avg*s[i];
}
ll G(int k,int l,int j)
{
    return F(k,j)-F(l,j);
}
ll H(int k,int l)
{
    return 2*(s[k]-s[l]);
}
ll dp(ll st)
{
    fo(i,st,st+n-1) b[i-st+1]=a[i];
    fo(i,1,n) s[i]=s[i-1]+b[i];
    memset(f,0,sizeof(f));
    f[0][0]=0;
    fo(i,1,n) f[i][1]=sqr(s[i]-avg);
    fo(j,2,k)
    {
        int head=1,tail=1;
        q[1]=j-1;
        fo(i,j,n)
        {
            while(head<tail && G(q[head+1],q[head],j-1)<s[i]*H(q[head+1],q[head])) head++;
            f[i][j]=f[q[head]][j-1]+sqr(s[i]-s[q[head]]-avg);
            while(head<tail && G(i,q[tail],j-1)*H(q[tail],q[tail-1])<G(q[tail],q[tail-1],j-1)*H(i,q[tail])) tail--;
            q[++tail]=i;
        }
    }
    return f[n][k];
}
int main()
{
    scanf("%lld %lld",&n,&k);
    fo(i,1,n) scanf("%lld",&a[i]),a[i]*=k,tot+=a[i],a[n+i]=a[i],avg+=a[i];
    fo(i,1,n+n) S[i]=S[i-1]+a[i];
    avg/=k;
    if(k==2)
    {
        ll ans=INF,sum=0,j=0;
        fo(i,1,n)
        {
            sum-=a[i-1];
            while(j<=i+n-1 && abs(sum-avg)>abs(sum+a[j+1]-avg)) sum+=a[++j];
            ans=min(ans,sqr(sum-avg)+sqr(tot-sum-avg));
        }
        printf("%lld",ans);
        return 0;
    }
    if(k==3)
    {
        ll ans=INF,sm=0,sm1=0,j=0,l=0;
        fo(i,1,n)
        {
            sm-=a[i-1];
            while(j<=i+n-1 && sm+a[j+1]<=avg) sm+=a[++j],sm1-=a[j];
            if(j>l) l=j,sm1=0;
            while(l<=i+n-3 && sm1+a[l+1]<=avg) sm1+=a[++l];
            fo(p1,max(1ll,j-1),min(i+n-1,j+1))
                fo(p2,max(p1+1,l-1),min(i+n-1,l+1))
                {
                    ll x=S[p1]-S[i-1],y=S[p2]-S[p1];
                    ans=min(ans,sqr(x-avg)+sqr(y-avg)+sqr(tot-x-y-avg));
                }
        }
        printf("%lld",ans);
        return 0;
    }
    ll ans=INF;
    fo(i,1,n) ans=min(ans,dp(i));
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值