​http://www.elijahqi.win/archives/3794​​​
Description
众所周知,贞鱼是一种高智商水生动物。不过他们到了陆地上智商会减半。
这不?他们遇到了大麻烦!
n只贞鱼到陆地上乘车,现在有k辆汽车可以租用。
由于贞鱼们并不能在陆地上自由行走,一辆车只能载一段连续的贞鱼。
贞鱼们互相有着深深的怨念,每一对贞鱼之间有怨气值。
第i只贞鱼与第j只贞鱼的怨气值记为Yij,且Yij=Yji,Yii=0。
每辆车载重不限,但是每一对在同辆车中的贞鱼都会产生怨气值。
当然,超级贞鱼zzp长者希望怨气值的总和最小。
不过他智商已经减半,想不出分配方案。
他现在找到了你,请你帮助他分配贞鱼们,并输出最小怨气值之和ans。

Input
第一行两个整数:n,k。
接下来读入一个n行n列的矩阵。矩阵中第i行j列的元素表示Yij。
当然这个矩阵是对称的。

Output
一个整数ans,表示:最小的怨气值之和
★注意:同辆车中,贞鱼i,j之间的怨气只算一次!
1 ≤ n ≤4000 ,1 ≤ k ≤min(n , 800) , 0 ≤ Yij≤10

Sample Input
8 3
0 1 1 1 1 1 1 1
1 0 1 1 1 1 1 1
1 1 0 1 1 1 1 1
1 1 1 0 1 1 1 1
1 1 1 1 0 1 1 1
1 1 1 1 1 0 1 1
1 1 1 1 1 1 0 1
1 1 1 1 1 1 1 0
Sample Output
7
编号为1,2,3的贞鱼一辆车:怨气值和为3;
编号为4,5,6的贞鱼一辆车:怨气值和为3;
编号为7,8的贞鱼一辆车:怨气值和为1。
最小怨气值总和为 3 + 3 + 1 = 7 。
设dp[i][j]表示前i个分成j段的代价
于是有朴素方程
然后利用决策单调性可以优化为n*k*log
即我们考虑其中两个决策点j1,j2对于后面的影响 因为这个s是单增的 所以如果dp[j1]>dp[j2]且j1< j2那么 j1将会永远不优 但是因为我们每次增加的这个东西并不是单调的
即我们考虑如果我们尝试去斜率优化可以发现 小于号右边的东西并不单调和所在决策点有关所以无法斜率优化 但是利用我们刚刚所说的决策单调性我们可以做到一个log 即每次入队的i时候比较一下队尾的元素相比起来谁不优的时间早如果后面的比前面的早说明队尾一定弹出

#include<bits/stdc++.h>
using namespace std;
inline char gc(){
static char now[1<<16],*S,*T;
if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
return *S++;
}
inline int read(){
int x=0,f=1;char ch=gc();
while(!isdigit(ch)) {if (ch=='-') f=-1;ch=gc();}
while(isdigit(ch)) x=x*10+ch-'0',ch=gc();
return x*f;
}
const int N=4040;
const int inf=0x3f3f3f3f;
int n,k,sum,s[N][N],K,dp[N][1000],q[N];
inline int gao(int x,int y){
return s[y][y]-s[y][x];
}
inline int calc(int x,int y){
int l=y+1,r=n,res=n+1;
while(l<=r){
int mid=l+r>>1;
if (dp[x][k-1]+gao(x,mid)>=dp[y][k-1]+gao(y,mid)) res=mid,r=mid-1;else l=mid+1;
}return res;
}
int main(){
freopen("bzoj5311.in","r",stdin);
n=read();K=read();sum=0;
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j) if(j<=i) s[i][j]=read(),sum+=s[i][j];else read();
for (int i=1;i<=n;++i) for (int j=1;j<=i;++j) s[i][j]+=s[i-1][j];
for (int i=1;i<=n;++i) for (int j=1;j<=i;++j) s[i][j]+=s[i][j-1];
for (int i=0;i<=n;++i) dp[i][0]=inf;dp[0][0]=0;
for (k=1;k<=K;++k){
int op=1,cl=1;q[op]=0;
for (int i=1,j;i<=n;++i){
while(op<cl&&calc(q[op],q[op+1])<=i)++op;
j=q[op];dp[i][k]=dp[j][k-1]+gao(j,i);
while(op<cl&&calc(q[cl-1],q[cl])>=calc(q[cl],i)) --cl;
q[++cl]=i;
}
//for (int i=1;i<=n;++i) printf("%d,dp[i][k]);puts("");
}printf("%d\n",dp[n][K]);
return 0;
}

对于k的限制因为bzoj上仍然过不去我们考虑wqs二分

在wqs二分的时候我限制了如果相同取越小越好 因为会存在例如3的时候比答案大4的时候比k小 因为枚举的整数所以才导致没有切上其实是两个点都相切造成的影响那么我们算答案的时候就必须知道答案点被属于了哪一个切点

注意细节!!! 可以看代码..

#include<bits/stdc++.h>
using namespace std;
inline char gc(){
static char now[1<<16],*S,*T;
if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
return *S++;
}
inline int read(){
int x=0,f=1;char ch=gc();
while(!isdigit(ch)) {if (ch=='-') f=-1;ch=gc();}
while(isdigit(ch)) x=x*10+ch-'0',ch=gc();
return x*f;
}
const int N=4040;
const int inf=0x3f3f3f3f;
int n,k,sum,s[N][N],K,dp[N],q[N],cnt[N];
inline int gao(int x,int y){
return s[y][y]-s[y][x];
}
inline int calc(int x,int y){
int l=y+1,r=n,res=n+1;
while(l<=r){
int mid=l+r>>1;
int t1=dp[x]+gao(x,mid),t2=dp[y]+gao(y,mid);
if (t1>t2||(t1==t2&&cnt[x]>cnt[y])) res=mid,r=mid-1;else l=mid+1;
}return res;
}
inline bool check(int mid){
int op=1,cl=1;q[op]=0;
for (int i=1,j;i<=n;++i){
while(op<cl&&calc(q[op],q[op+1])<=i) ++op;
j=q[op];dp[i]=dp[j]+gao(j,i)+mid;cnt[i]=cnt[j]+1;
while(op<cl&&calc(q[cl-1],q[cl])>calc(q[cl],i)) --cl;
q[++cl]=i;
}return cnt[n]<=K;
}
int main(){
freopen("bzoj5311.in","r",stdin);
n=read();K=read();sum=0;
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j) if(j<=i) s[i][j]=read(),sum+=s[i][j];else read();
for (int i=1;i<=n;++i) for (int j=1;j<=i;++j) s[i][j]+=s[i-1][j];
for (int i=1;i<=n;++i) for (int j=1;j<=i;++j) s[i][j]+=s[i][j-1];
int l=0,r=sum,ans=0,nm,v;
while(l<=r){
int mid=l+r>>1;
if(check(mid)) ans=dp[n]-mid*K,r=mid-1;else l=mid+1;
}
printf("%d\n",ans);
return 0;
}