【斜率优化】bzoj3675-[Apio2014]序列分割&&Uoj104

题目大意

将一个长度为N的非负整数序列分割成k+l个非空的子序列,每次选择一位置分割后,将会得到一定的分数,这个分数为两个新序列中元素和的乘积。求最大的分数。
[UOJ104]并输出任意一种方案

思路

显然,无论分割顺序如何,不会影响最后得到的结果。所以可以利用递推方程。\(f[i][j]\)表示取前\(i\)个数,分割成\(j\)个序列能得到的最大分数。显然有:

\[f[i][k]=max(f[j][k-1]+sum[j]*(sum[i]-sum[k])) \]

\(Ans_{j_1}>Ans_{j_2}\)时,有:

\[f[j_1][k-1]+sum[j_1]*sum[i]-sum[j_1]^2 \]

\[\gt \]

\[f[j_2][k-1]+sum[j_2]*sum[i]-sum[j_2]^2 \]

\(x[i]=f[i][k-1]-sum[i]^2,y[i]=sum[i]\)
则有:

\[-sum[i]<\frac{x[j_1]-x[j_2]}{y[j_1]-y[j_2]} \]

注意点

计算斜率的时候\(x_1\)可能等于\(x_2\),特判一下将斜率设为INF或-INF。不要忘记开long long。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int MAXN=100100;
const int MAXK=250;
int n,k;
LL sum[MAXN],x[2][MAXN],g[MAXN],y[MAXN],f[MAXN][2];
int cur;

void init()
{
    scanf("%d%d",&n,&k);    
    for (int i=1;i<=n;i++) 
    {
        int tmp;
        scanf("%d",&tmp);
        y[i]=sum[i]=sum[i-1]+tmp;
        g[i]=-y[i];
    }
}

LL dp()
{
    memset(f,0,sizeof(f));
    cur=0;
    for (int i=1;i<=n;i++) x[1-cur][i]=f[i][cur]-(sum[i]*sum[i]);
    for (int j=2;j<=k+1;j++)
    {
        cur=1-cur;
        int head=0,tail=1,que[MAXN];
        for (int i=j-1;i<=n;i++)//上一次至多分割为j-1部分,则至少从j-1开始 
        {
            while (head+1<tail && x[cur][que[head]]-x[cur][que[head+1]]<=g[i]*(y[que[head]]-y[que[head+1]])) head++;
            int best=que[head];
            f[i][cur]=f[best][1-cur]+sum[best]*(sum[i]-sum[best]);
            while (head+1<tail && (LL)(x[cur][que[tail-1]]-x[cur][i])*(y[que[tail-2]]-y[que[tail-1]])>=(LL)(x[cur][que[tail-2]]-x[cur][que[tail-1]])*(y[que[tail-1]]-y[i])) tail--;
            que[tail++]=i;
            x[1-cur][i]=f[i][cur]-(sum[i]*sum[i]);
        }
    }
    return (f[n][cur]);
}

void printans()
{
    printf("%lld\n",dp());
}

int main()
{
    init();
    printans();
    return 0;   
} 

输出方案

只需记录一下路径就好了。不过要注意,UOJ后面数据时间卡得非常可啪,所以我们不用斜率而是直接用乘法来计算,同时x数组y数组g数组也不要了直接套进去算,勉勉强强卡过了……[痛心疾首.jpg]
顺带一提的是,这样的话BZOJ会T(咦?)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int MAXN=100100;
const int MAXK=250;
int n,k;
LL sum[MAXN],f[MAXN][2],fr[MAXN][MAXK];
int cur;


void init()
{
    scanf("%d%d",&n,&k);    
    for (int i=1;i<=n;i++) 
    {
        int tmp;
        scanf("%d",&tmp);
        sum[i]=sum[i-1]+tmp;
    }
}

LL x(int m)
{
	return f[m][1-cur]-sum[m]*sum[m];
}

LL dp()
{
    memset(f,0,sizeof(f));
    cur=0;
    for (int j=2;j<=k+1;j++)
    {
        cur=1-cur;
        int head=0,tail=1,que[MAXN];
        for (int i=j-1;i<=n;i++)//上一次至多分割为j-1部分,则至少从j-1开始 
        {
            while (head+1<tail && (f[que[head]][1-cur]-(LL)(sum[que[head]]*sum[que[head]])-f[que[head+1]][1-cur]+(LL)(sum[que[head+1]]*sum[que[head+1]])<=(LL)-sum[i]*(sum[que[head]]-sum[que[head+1]]))) head++;
            int best=que[head];
            f[i][cur]=f[best][1-cur]+sum[best]*(sum[i]-sum[best]);
            fr[i][j]=best;
            while (head+1<tail && (LL)(x(que[tail-1])-x(i))*(sum[que[tail-2]]-sum[que[tail-1]])>=(LL)(x(que[tail-2])-x(que[tail-1]))*(sum[que[tail-1]]-sum[i])) tail--;
            que[tail++]=i;
        }
    }
    return (f[n][cur]);
}

void printans()
{
    printf("%lld\n",dp());
    int ans[MAXK];
	memset(ans,0,sizeof(ans));//不要忘记初始化★
    for (int i=k+1;i>=2;i--)
    {
    	ans[++ans[0]]=fr[n][i];
    	n=fr[n][i];
	}
	for (int i=ans[0];i>=1;i--) printf("%d ",ans[i]);
}

int main()
{
    init();
    printans();
    return 0;   
} 
posted @ 2016-08-02 11:36  iiyiyi  阅读(163)  评论(0编辑  收藏  举报