题目描述
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天来走的最小平方和
所以
接下来有几种做法:
1、决策单调性优化DP
设s[i][j]为f[i][j]的最优决策点
则不难发现s[i][j-1]<=s[i][j](你多分一块肯定是要让总体更均匀,当前段的决策点就不应该向左移)
所以记录一下上一次的最优决策点就可以了
代码:(本校OJ410ms)
(后来跟大佬们讨论了一下,发现它实际上是O(n^3),因为后面有很多没有用的决策点都会枚举,但是它很难卡(应该是吧))
#include
#include
#include
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
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
#include
#include
#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=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>1;
if(calc(mid)
你可能感兴趣的:(动态规划,分治,DP,斜率优化,决策单调性,wqs二分,带权二分)