BZOJ4518征途(斜率优化DP || 决策单调性DP || wqs二分+斜率优化DP)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/C20180602_csq/article/details/102592488

征途

题目描述

Pine开始了从S地到T地的征途。

从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站。

Pine计划用m天到达T地。除第m天外,每一天晚上Pine都必须在休息站过夜。所以,一段路必须在同一天中走完。

Pine希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。

帮助Pine求出最小方差是多少。

设方差是v,可以证明,v×m^2是一个整数。为了避免精度误差,输出结果时输出v×m^2。

输入格式

第一行两个数 n、m。

第二行 n 个数,表示 n 段路的长度

输出格式

一个数,最小方差乘以 m^2 后的值

输入样例 

5 2
1 2 5 8 6

输出样例 

36

提示

1≤n≤3000,保证从 S 到 T 的总路程不超过 30000

题解

看到方差,先化一下式子

最后可以发现21世纪最伟大的发现:

一组数据的方差=每个数平方的平均数 - 所有数平均数的平方

接下来就可以DP了

设f[i][j]为前i段路分成j天来走的最小方差???

当然不是这样做

由于所有数之和是定值,分的组数也是定值,所以所有数的平均数也是定值

我们只需要前面的平方平均数最小

所以就设f[i][j]为前i段路分成j天来走的最小平方和

所以f[i][j]=min(f[k][j-1]+(sum[i]-sum[k])^2)

接下来有几种做法:

1、决策单调性优化DP

设s[i][j]为f[i][j]的最优决策点

则不难发现s[i][j-1]<=s[i][j](你多分一块肯定是要让总体更均匀,当前段的决策点就不应该向左移)

所以记录一下上一次的最优决策点就可以了

代码:(本校OJ410ms)

(后来跟大佬们讨论了一下,发现它实际上是O(n^3),因为后面有很多没有用的决策点都会枚举,但是它很难卡(应该是吧))

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 3005
long long f[N][N],tp[N],a[N],sum[N];
int main()
{
	int n,m,i,j,k;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	memset(f,0x3f,sizeof(f));f[0][0]=0;
	for(j=1;j<=m;j++){
		for(i=1;i<=n;i++){
			for(k=tp[i];k<=i;k++){
				long long tmp=1ll*f[k][j-1]+1ll*(sum[i]-sum[k])*(sum[i]-sum[k]);
				if(f[i][j]>tmp){
					f[i][j]=tmp;
					tp[i]=k;
				}
			}
		}
	}
	printf("%lld",1ll*m*f[n][m]-1ll*sum[n]*sum[n]);
}

2、斜率优化DP

把平方展开,选两个决策点来比较优劣,然后把含sum[i]的一项放在一边,其他项放在另一边,分类讨论一下就可以发现维护下凸包。

代码(by  Ark):(本校OJ 111ms)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 3005;
int n, m, q[MAXN], a[MAXN];
LL f[2][MAXN], x[MAXN], y[MAXN];
inline LL Cross(LL a, LL b, LL c, LL d) { return a * d - b * c; }
int main () {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) scanf("%d", &a[i]), a[i] += a[i-1];
    int cur = 0; memset(f[cur], 0x3f, sizeof f[cur]);
    f[cur][0] = 0;
    for(int j = 1; j <= m; ++j) {
        cur ^= 1; memset(f[cur], 0x3f, sizeof f[cur]);
        int s = 0, t = 0;
        for(int i = 1; i <= n; ++i) {
            x[i-1] = a[i-1]; y[i-1] = f[cur^1][i-1] + a[i-1] * a[i-1];
            while(s+1 < t && Cross(x[q[t-1]]-x[q[t-2]], y[q[t-1]]-y[q[t-2]], x[i-1]-x[q[t-1]], y[i-1]-y[q[t-1]]) <= 0) --t;
            q[t++] = i-1;
            while(s+1 < t && y[q[s+1]]-y[q[s]] <= (x[q[s+1]]-x[q[s]]) * 2 * a[i]) ++s;
            f[cur][i] = f[cur^1][q[s]] + (a[i]-a[q[s]])*(a[i]-a[q[s]]);
        }
    }
    printf("%lld\n", f[cur][n] * m - a[n] * a[n]);
}

3、wqs带权二分+斜率优化DP:

当没有段数的限制时,我们可以直接O(n)斜率优化做

发现平方和关于段数的函数是个凸函数(应该是吧)。。。

我们就对分的每一段加一个权值,然后直接斜率优化DP,记录一下段数,然后来二分这个权值,直到段数变成m

代码(by Master.Yi):(本校OJ 0ms)

#include<cstdio>
#include<cctype>
#include<algorithm>
#define maxn 3005
#define LL long long
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
inline void read(int &a){
    char c;bool f=0;while(!isdigit(c=getc())) c=='-'&&(f=1);
    for(a=c-'0';isdigit(c=getc());a=a*10+c-'0'); f&&(a=-a);
}
int n,m,cnt[maxn],s[maxn],q[maxn],h,t;
LL f[maxn];
inline LL gety(int i,int j){return f[j]+s[j]*s[j]-f[i]-s[i]*s[i];}
inline LL getx(int i,int j){return s[j]-s[i];}
int calc(int Mid){
    h=t=1;
    for(int i=1;i<=n;i++){
        while(h<t&&gety(q[h],q[h+1])<=getx(q[h],q[h+1])*2*s[i]) h++;
        f[i]=f[q[h]]+(s[i]-s[q[h]])*(s[i]-s[q[h]])+Mid,cnt[i]=cnt[q[h]]+1;
        while(h<t&&gety(q[t-1],q[t])*getx(q[t],i)>=gety(q[t],i)*getx(q[t-1],q[t])) t--;
        q[++t]=i;
    }
    return cnt[n];
}
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
    #endif
    read(n),read(m);
    for(int i=1;i<=n;i++) read(s[i]),s[i]+=s[i-1];
    int l=0,r=s[n]*s[n],mid;
    while(l<r){
        mid=(l+r+1)>>1;
        if(calc(mid)<m) r=mid-1;
        else l=mid;
    }
    calc(l);
    printf("%lld\n",m*(f[n]-1ll*m*l)-s[n]*s[n]);
}

猜你喜欢

转载自blog.csdn.net/C20180602_csq/article/details/102592488
今日推荐