1. 程式人生 > >LG3648 [APIO2014]序列分割

LG3648 [APIO2014]序列分割

題意

你正在玩一個關於長度為 \(n\) 的非負整數序列的遊戲。這個遊戲中你需要把序列分成 \(k+1\) 個非空的塊。為了得到 \(k+1\) 塊,你需要重複下面的操作 \(k\) 次:

選擇一個有超過一個元素的塊(初始時你只有一塊,即整個序列)

選擇兩個相鄰元素把這個塊從中間分開,得到兩個非空的塊。

每次操作後你將獲得那兩個新產生的塊的元素和的乘積的分數。你想要最大化最後的總得分。

\(2≤n≤100000,1≤k≤\min\{n−1,200\}\)

分析

參照Tunix的題解。

首先我們根據這個分割的過程可以發現:總得分等於k+1段兩兩的乘積的和(乘法分配律),也就是說與分割順序是無關的。

再對乘積進行重分組(還是乘法分配律)我們可以轉化為:ans=∑第 i 段×前 i-1 段的和

所以我們就可以以分割次數為階段進行DP啦~

\(f[i][j]\)表示將前 \(j\) 個數分成 \(i\) 段的最大得分,那麼就有
\[ f[i][j]=\max\{f[i−1][k]+s[k]×(s[j]−s[k])\} \]
這裡我前些時候一直規範要求的作用就體現出來了。

\(x>y\),且\(x\)\(y\)優,則
\[ f[i-1][x]-s[x]^2+s[x]s[j]>f[i-1][y]-s[y]^2+s[y]s[j] \]
這裡就不能亂移項,必須保證除式中的\(\varphi(x)-\varphi(y)\)

值是正的才談得上平面中的點。必須把\(s[j]\)的係數調整成\(s[x]-s[y]\)
\[ \frac{s[x]^2-f[i-1][x]-s[y]^2+f[i-1][y]}{s[x]-s[y]}<s[j] \]
這才是正確的斜率式,跟前面的\(f[i-1][x]-s[x]^2\)是反的。

還是多次的斜率優化,洛谷上還要輸出方案數,這也比較簡單,每次轉移的時候記一個\(g\)陣列記錄就行了。

時間複雜度\(O(kn)\)

程式碼

首先是BZOJ同名題,卡空間需要滾動陣列的。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>
#include<bitset>
#include<cassert>
#include<ctime>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read()
{
    rg T data=0;
    rg int w=1;
    rg char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            w=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        data=data*10+ch-'0';
        ch=getchar();
    }
    return data*w;
}
template<class T>T read(T&x)
{
    return x=read<T>();
}
using namespace std;
typedef long long ll;

co int K=201,N=1e5+1;
ll s[N];
ll f[2][N]; // edit 1: MLE

ll Up(int i,int x,int y)
{
    return s[x]*s[x]-f[i^1][x]-s[y]*s[y]+f[i^1][y];
}

ll Down(int x,int y)
{
    return s[x]-s[y];
}

ll Cal(int i,int j,int k)
{
    return f[i^1][k]+s[k]*(s[j]-s[k]);
}

int q[N];

int main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
//  cerr<<"sizef="<<(sizeof(f)+sizeof(s)+sizeof(q))/1024.0/1024<<endl;
    int n=read<int>(),k=read<int>();
    for(int i=1;i<=n;++i)
        s[i]=s[i-1]+read<int>();
    for(int i=1;i<=k;++i)
    {
        int head=0,tail=0;
        q[tail++]=0;
        for(int j=1;j<=n;++j)
        {
            while(head+1<tail&&Up(i&1,q[head+1],q[head])<=s[j]*Down(q[head+1],q[head]))
                ++head;
            f[i&1][j]=Cal(i&1,j,q[head]);
            while(head+1<tail&&Up(i&1,j,q[tail-1])*Down(q[tail-1],q[tail-2])<=Up(i&1,q[tail-1],q[tail-2])*Down(j,q[tail-1]))
                --tail;
            q[tail++]=j;
        }
    }
    printf("%lld\n",f[k&1][n]);
    return 0;
}

然後是洛谷上需要輸出方案,但不卡空間的。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>
#include<bitset>
#include<cassert>
#include<ctime>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read()
{
    rg T data=0;
    rg int w=1;
    rg char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            w=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        data=data*10+ch-'0';
        ch=getchar();
    }
    return data*w;
}
template<class T>T read(T&x)
{
    return x=read<T>();
}
using namespace std;
typedef long long ll;

co int K=201,N=1e5+1;
ll s[N];
ll f[K][N];
int g[K][N];

ll Up(int i,int x,int y)
{
    return s[x]*s[x]-f[i-1][x]-s[y]*s[y]+f[i-1][y];
}

ll Down(int x,int y)
{
    return s[x]-s[y];
}

ll Cal(int i,int j,int k)
{
    return f[i-1][k]+s[k]*(s[j]-s[k]);
}

int q[N];

int main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
//  cerr<<"sizef="<<(sizeof(f)+sizeof(s)+sizeof(q))/1024.0/1024<<endl;
    int n=read<int>(),k=read<int>();
    for(int i=1;i<=n;++i)
        s[i]=s[i-1]+read<int>();
    for(int i=1;i<=k;++i)
    {
        int head=0,tail=0;
        q[tail++]=0;
        for(int j=1;j<=n;++j)
        {
            while(head+1<tail&&Up(i,q[head+1],q[head])<=s[j]*Down(q[head+1],q[head]))
                ++head;
            f[i][j]=Cal(i,j,q[head]);
            g[i][j]=q[head];
            while(head+1<tail&&Up(i,j,q[tail-1])*Down(q[tail-1],q[tail-2])<=Up(i,q[tail-1],q[tail-2])*Down(j,q[tail-1]))
                --tail;
            q[tail++]=j;
        }
    }
    printf("%lld\n",f[k][n]);
    stack<int>sol;
    for(int i=k,j=n;i>=1;--i)
    {
        j=g[i][j];
        sol.push(j);
    }
    while(sol.size())
    {
        printf("%d ",sol.top());
        sol.pop();
    }
    return 0;
}

個人比較喜歡洛谷上的題。卡空間算一種噁心的卡常,況且寫起來還那麼醜。