货物分组(加强版)
设:
其中 \(f_{i,x}\) 表示到第 \(i\) 个位置,分 \(x\) 组的花费。
记起来某道题,得到了启发,想到了优化 DP 式的方法。
初始化 \(f_0\) 为 \(sum\)。
等于把这个费用塞到前面而非后面,就可以不用记录 \(x\) 了。
具体的,就是维护一个单调队列。
for(i=1;i<=n;i++)
{
for(;head<=tail&&add[i]-add[d[head]]>W;head++);
f[i]=0x3f3f3f3f3f3f3f3f;
for(j=head;j<=tail;j++)
{
int x=d[j]+1;
int S=work(x,i);//线段树直接跑区间最值
if(f[x-1]+add[n]-add[i]+S<f[i])
{
f[i]=f[x-1]+add[n]-add[i]+S;
}
}
// if(deep[i]<deep[i-1])printf("false\n");
for(;head<=tail&&f[i]<=f[d[tail]];tail--);
d[++tail]=i;
}
考虑优化:
如果记录这个分组数 \(x\),表示的是到当前位置分 \(x\) 组最优。
设这个东西为 \(deep_i\)。
\(deep_i\) 单调不减,这个应该是比较显然的,当然如果你觉得有哪里说不过去,也欢迎指出。
有了 \(deep_i\) 单调不减的结论,加上从二维 DP 的式子中可以知道:每个组数为 \(x\) 的都由一个 \(x-1\) 得来,我们就可以在代码里加上这样的优化:
for(;deep[d[head]]+1<deep[i];head++);
简单的理解:当前的 \(deep\) 已经比队头的 \(deep\) 至少大 \(2\) 了,后面的 \(deep\) 不减,那么队头元素就再也用不到了,弹出。
至于为什么可以这样操作,因为既然 \(deep\) 是当前最优分组数,那么其他的分组数就都是无用的策略。(回归到一维 DP 的角度考虑)
\(\operatorname {Question}:\) 这不就是常数优化吗?感觉完全能卡到 \(\operatorname O(n^2)\)
对于这个问题,我一开始认为我这个代码,对于每个相同最优分组数的位置,我只会保存最后一个。但实际上不是,只需要出现这样的连续数对 \(y_1,\dots ,y_n,x_1,x_2\) 满足 \(x_2\) 加入后 \(S\) 的增大值大于 \(x_2\) 便足以推翻这个结论。
那么一种很差的数据(我个人感觉是最坏的情况,但不敢肯定),就是:
这样的话,对于每一组相同 \(deep\) 的 \(f\) 值,我全部都得保留,然后因为我每次最多保留两组数,所以我这个代码的理论复杂度为 \(\operatorname O(2n\log \operatorname W \log n)\)。
那么 \(10^5\) 的数据是绰绰有余的,但是对于加强的数据 \(n\le 6\times 10^6,\operatorname W\le 10^7\),该怎么跑过去呢?
如果把线段树换成 st 表,这个 \(\log n\) 就可以删去,我这波将绝杀,但是问题也很明显:空间会炸。
可惜换不得
但是,算了算,这个空间是 \(522\) MB 左右。
搞了个期望 \(\operatorname O(1)\) 的分块 RMQ,总算是过了这题。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=6e6+3;
const int M=2460;
struct milk
{
int l,r;
int s1_l[M],s1_r[M];
int s2_l[M],s2_r[M];
}t[M];
int s_1[M][M];
int s_2[M][M];
int to[N];
int a[N];
LL f[N];
LL add[N];
int deep[N];
int d[N];
int n,W;
int nam;
int maxx,minx;
inline void init()
{
int lens=sqrt(n);
int ks=n/lens;
for(int i=1;i<=ks;i++)
{
t[i].l=t[i-1].r+1;
t[i].r=lens*i;
t[i].s1_l[0]=t[i].s1_r[lens+1]=0;
t[i].s2_l[0]=t[i].s2_r[lens+1]=0x3f3f3f3f;
for(int j=1;j<=lens;j++)
{
t[i].s1_l[j]=max(t[i].s1_l[j-1],a[j+t[i].l-1]);
t[i].s2_l[j]=min(t[i].s2_l[j-1],a[j+t[i].l-1]);
}
for(int j=lens;j>=1;j--)
{
t[i].s1_r[j]=max(t[i].s1_r[j+1],a[j+t[i].l-1]);
t[i].s2_r[j]=min(t[i].s2_r[j+1],a[j+t[i].l-1]);
}
}
if(n%lens>0)
{
ks++;
t[ks].l=t[ks-1].r+1;
t[ks].r=n;
lens=t[ks].r-t[ks].l+1;
int i=ks;
t[i].s1_l[0]=t[i].s1_r[lens+1]=0;
t[i].s2_l[0]=t[i].s2_r[lens+1]=0x3f3f3f3f;
for(int j=1;j<=lens;j++)
{
t[i].s1_l[j]=max(t[i].s1_l[j-1],a[j+t[i].l-1]);
t[i].s2_l[j]=min(t[i].s2_l[j-1],a[j+t[i].l-1]);
}
for(int j=lens;j>=1;j--)
{
t[i].s1_r[j]=max(t[i].s1_r[j+1],a[j+t[i].l-1]);
t[i].s2_r[j]=min(t[i].s2_r[j+1],a[j+t[i].l-1]);
}
}
for(int i=1;i<=ks;i++)
{
s_1[i][i]=t[i].s1_r[1];
s_2[i][i]=t[i].s2_r[1];
for(int j=i+1;j<=ks;j++)
{
s_1[i][j]=max(s_1[i][j-1],t[j].s1_r[1]);
s_2[i][j]=min(s_2[i][j-1],t[j].s2_r[1]);
}
for(int j=t[i].l;j<=t[i].r;j++)to[j]=i;
}
return;
}
inline int work(int x,int y)
{
int maxx,minx;
if(to[x]==to[y])
{
maxx=0,minx=0x3f3f3f3f;
for(int i=x;i<=y;i++)maxx=max(maxx,a[i]),minx=min(minx,a[i]);
return maxx-minx;
}
int l=to[x],r=to[y];
maxx=max(t[l].s1_r[x-t[l].l+1],t[r].s1_l[y-t[r].l+1]);
minx=min(t[l].s2_r[x-t[l].l+1],t[r].s2_l[y-t[r].l+1]);
if(l+2<=r)
{
maxx=max(maxx,s_1[l+1][r-1]);
minx=min(minx,s_2[l+1][r-1]);
}
return maxx-minx;
}
int rin()
{
int s=0;
char c=getchar();
bool bj=0;
for(;(c>'9'||c<'0')&&c!='-';c=getchar());
if(c=='-')c=getchar(),bj=true;
for(;c>='0'&&c<='9';c=getchar())s=(s<<1)+(s<<3)+(c^'0');
if(bj)return -s;
return s;
}
int main()
{
// freopen("data24.in","r",stdin);
// freopen("data24.out","w",stdout);
int i,j;
n=rin();W=rin();
for(i=1;i<=n;i++)
{
a[i]=rin();
add[i]=add[i-1]+a[i];
}
init();
int head=1,tail=0;
d[++tail]=0;
f[0]=add[n];
deep[0]=0;
for(i=1;i<=n;i++)
{
for(;head<=tail&&add[i]-add[d[head]]>W;head++);
f[i]=0x3f3f3f3f3f3f3f3f;
for(j=head;j<=tail;j++)
{
int x=d[j]+1;
int S=work(x,i);
if(f[x-1]+add[n]-add[i]+S<f[i])
{
f[i]=f[x-1]+add[n]-add[i]+S;
deep[i]=deep[d[j]]+1;
}
}
for(;deep[d[head]]+1<deep[i];head++);
for(;head<=tail&&f[i]<=f[d[tail]];tail--);
d[++tail]=i;
}
printf("%lld\n",f[n]);
return 0;
}