把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

luogu P4983 忘情

题面传送门
wqs二分可真是个神奇的方法。
首先这道题那个式子化简一下就是\((\sum{x_i}+1)^2\)
易得设选\(k\)个为\(f(k)\)这个东西是个单调下降的函数,且是个凸函数。
这个东西斜率优化显然可以做到\(O(nm)\)
具体的,转移方程是\(dp_{i,k}=\min\limits_{j=1}^{i-1}{dp_{j,k-1}+(sum_i-sum_j+1)^2}\)
展开后一顿乱化变成\(2sum_i>\frac{X_j-X_k}{q_j-q_k}\),其中\(X_a=dp_a+sum_a^2-2\times sum_a\)
不过虽然过不了但是其实后面有用。
可以发现如果我们拿一条斜率小于0的直线去截那个凸壳,直线斜率越大切点越小。
而我们最终目的是让切点落在\(m\)上。
所以我们可以二分斜率,然后算出最优转移点。
然而为了和这个直线切到,我们每次转移要减去\(k\)
这个直接斜率优化不难。
其它照旧即可。
注意找到转移点时如果多个转移相同转移选的最大的点因为我们的目的是要让最小的转移到\(m\)点。
时间复杂度\(O(nlogn)\)
code:

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<algorithm>
#include<bitset>
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define l(x) x<<1
#define r(x) x<<1|1
#define re register
#define ll long long
#define db long double
#define W 10000000
#define N 100000
#define K (1<<10)
#define eps (1e-6)
#define mod 1000000007
#define X(a) (dp[a]+sum[a]*sum[a]-2*sum[a])
using namespace std;
int n,m,k,q[N+5],head,tail;ll dp[N+5],sum[N+5],g[N+5],a[N+5],l,r,mid;
I db slope(int x,int y){return (db)(X(y)-X(x))/(sum[y]-sum[x]);}
I int check(ll mid){
	re int i,j;q[head=tail=0]=0;
	for(i=1;i<=n;i++){
		while(head<tail&&(slope(q[head],q[head+1])<2*sum[i]||(slope(q[head],q[head+1])==2*sum[i]&&g[q[head]]<g[q[head+1]]))) head++;
		j=q[head];dp[i]=dp[j]+(sum[i]-sum[j]+1)*(sum[i]-sum[j]+1)-mid;g[i]=g[j]+1;
		while(head<tail&&(slope(q[tail-1],q[tail])>slope(q[tail],i)||(slope(q[tail-1],q[tail])==slope(q[tail],i)&&g[i]>g[q[tail]]))) tail--;q[++tail]=i;
	}
	return g[n]>=m;
}
int main(){
	freopen("1.in","r",stdin);
	re int i;scanf("%d%d",&n,&m);for(i=1;i<=n;i++) scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];l=-1e16;r=0;
	while(l+1<r) mid=l+r>>1,(check(mid)?r:l)=mid;check(r);printf("%lld\n",dp[n]+r*m);
} 
posted @ 2021-05-21 22:09  275307894a  阅读(48)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end