標籤:int 開始 ring while scanf 傳送門 turn algorithm http
【傳送門:BZOJ3675】簡要題意:
一開始給出n個數的一段序列,可以分割k次,每次只能分割一段序列,一段序列被分割後就變成兩個序列,每次分割的價值為分割的位置左邊的數的和乘右邊的數的和
求出最大價值
題解:
DP+斜率最佳化
首先來設f[i][k]為前i個數,分割k次得到的最大價值
接下來。。。
方程。。。。
方程。。。
方程。。
方程。
需要玄學思考
假設一段序列為4|1 2|3(|表示分割)
那麼f[2][1]=4*1=4
f[4][2]=4*(1+2+3)+(1+2)*3=f[2][1]+(4+1)*(3+2)!!
其實這裡用了乘法分配律
太棒了這樣就得到f[i][k]=max(f[j][k-1]+sum[j]*(sum[i]-sum[j]))
然後滾動數組f[i][now]=max(f[j][last]+sum[j]*(sum[i]-sum[j]))
再用斜率最佳化,美滋滋
設j1<j2<i
得到
f[j2][last]+sum[j2]*(sum[i]-sum[j2])>f[j1][last]+sum[j1]*(sum[i]-sum[j1])
f[j2][last]-f[j1][last]+sum[j1]^2-sum[j2]^2>(sum[j1]-sum[j2])*sum[i]
(f[j2][last]-f[j1][last]+sum[j1]^2-sum[j2]^2)/(sum[j1]-sum[j2])<sum[i]
因為sum[j1]-sum[j2]有可能等於0,所以在處理隊列的時候,如果存在sum[j1]-sum[j2],要將j1給除掉,保留j2
注意加long long
參考代碼:
#include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<algorithm>using namespace std;typedef long long LL;//f[i][now]=max(f[j][last]+sum[j]*(sum[i]-sum[j]))//j1<j2<i//f[j2][last]+sum[j2]*(sum[i]-sum[j2])>f[j1][last]+sum[j1]*(sum[i]-sum[j1])//f[j2][last]-f[j1][last]+sum[j1]^2-sum[j2]^2>(sum[j1]-sum[j2])*sum[i]//(f[j2][last]-f[j1][last]+sum[j1]^2-sum[j2]^2)/(sum[j1]-sum[j2])<sum[i]LL f[110000][2];LL sum[110000];int now=1,last=0;LL slop(int j1,int j2){ return (f[j2][last]-f[j1][last]-sum[j2]*sum[j2]+sum[j1]*sum[j1])/(sum[j1]-sum[j2]);}int list[110000];int main(){ int n,K; scanf("%d%d",&n,&K); sum[0]=0; for(int i=1;i<=n;i++) { int d; scanf("%d",&d); sum[i]=sum[i-1]+d; } memset(f,0,sizeof(f)); for(int k=1;k<=K;k++) { now^=1;last^=1; int head=1,tail=0;list[head]=0; for(int i=k;i<=n;i++) { while(head<tail&&(sum[list[head]]==sum[list[head]+1]||slop(list[head],list[head+1])<sum[i])) head++; int j=list[head]; f[i][now]=f[j][last]+sum[j]*(sum[i]-sum[j]); while(head<tail&&(sum[i]==sum[list[tail]]||slop(list[tail-1],list[tail])>slop(list[tail],i))) tail--; list[++tail]=i; } } printf("%lld\n",f[n][now]); return 0;}
BZOJ3675: [Apio2014]序列分割