题面
购物
题解
决策单调性全忘了……
先考虑暴力怎么做,我们可以设
f
i
,
j
f_{i,j}
fi,j? 表示前
i
i
i 个商店买了
j
j
j 件物品的最小代价,然后有转移:
f
i
,
j
=
min
?
k
=
0
j
(
f
i
?
1
,
k
+
s
a
i
,
j
?
k
)
f_{i,j}=\min_{k=0}^j(f_{i-1,k}+sa_{i,j-k})
fi,j?=k=0minj?(fi?1,k?+sai,j?k?) 其中
s
a
i
,
j
sa_{i,j}
sai,j? 表示
a
i
,
?
a_{i,*}
ai,?? 的前缀和。
我们先来看一个决策单调性优化 DP 的模板题:给定两个长度为
n
n
n 的数组
f
,
g
f,g
f,g,其中
f
,
g
f,g
f,g 均单调不降,且
g
g
g 的差分数组
Δ
g
\Delta g
Δg 单调不降,令
h
k
=
min
?
i
+
j
=
k
(
f
i
+
g
j
)
h_{k}=\min\limits_{i+j=k}(f_i+g_j)
hk?=i+j=kmin?(fi?+gj?),求
h
h
h。
结论是:随着
k
=
i
+
j
k=i+j
k=i+j 增大,最优决策点
i
i
i 单调不降。
证明:考虑对每个
i
i
i 绘出
f
i
f_i
fi? 对
h
k
h_{k}
hk? 贡献的图像,设为
f
i
(
k
)
f_i(k)
fi?(k),那么对于任意两个
i
1
<
i
2
i_1<i_2
i1?<i2? 和它们的函数图像:
这个图像包含的性质有:
f
i
1
(
k
)
f_{i_1}(k)
fi1??(k) 和
f
i
2
(
k
)
f_{i_2}(k)
fi2??(k) 形状相同、
f
i
2
(
k
)
f_{i_2}(k)
fi2??(k) 起点在
f
i
1
(
k
)
f_{i_1}(k)
fi1??(k) 右上方、
f
i
1
(
k
)
,
f
i
2
(
k
)
f_{i_1}(k),f_{i_2}(k)
fi1??(k),fi2??(k) 单调不降且导数不降。
那么必然存在一个阈值
X
X
X(两图像交点),使得在
X
X
X 之前都是
f
i
1
(
k
)
f_{i_1}(k)
fi1??(k) 更优、在
X
X
X 之后都是
f
i
2
(
k
)
f_{i_2}(k)
fi2??(k) 更优。
也就说明了,不存在
k
1
<
k
2
k_1<k_2
k1?<k2? 满足在
k
=
k
1
k=k_1
k=k1? 时
f
i
2
(
k
)
f_{i_2}(k)
fi2??(k) 更优且在
k
=
k
2
k=k_2
k=k2? 时
f
i
1
(
k
)
f_{i_1}(k)
fi1??(k) 更优。即随着
k
k
k 增大,最优决策点
i
i
i 单调不降。
有了这个结论之后还不能直接做,因为虽然最优决策点
i
i
i 单调不降,但它不一定连续,即随着
k
k
k 增大,
i
i
i 可能是跳跃前进的。
正确的方法是使用分治:我们暴力找到
k
=
m
i
d
k=mid
k=mid 时的最优决策点
i
0
i_0
i0?,然后我们就知道了当
k
<
m
i
d
k<mid
k<mid 时的最优决策点区间
[
0
,
i
0
]
[0,i_0]
[0,i0?],和当
k
>
m
i
d
k>mid
k>mid 时的最优决策点区间
[
i
0
,
n
]
[i_0,n]
[i0?,n],然后再分治下去做即可。一共
O
(
log
?
n
)
O(\log n)
O(logn) 层,每层暴力找最优决策点的时间总和为
O
(
n
)
O(n)
O(n),时间复杂度
O
(
n
log
?
n
)
O(n\log n)
O(nlogn)。
再看回这一题:
f
n
,
k
=
min
?
i
+
j
=
k
(
f
n
?
1
,
i
+
s
a
n
,
j
)
f_{n,k}=\min_{i+j=k}(f_{n-1,i}+sa_{n,j})
fn,k?=i+j=kmin?(fn?1,i?+san,j?) 观察到式子中
f
n
?
1
,
?
f_{n-1,*}
fn?1,?? 和
s
a
n
,
?
sa_{n,*}
san,?? 是单调不降的,
s
a
n
,
?
sa_{n,*}
san,?? 的差分数组
Δ
s
a
n
,
?
\Delta sa_{n,*}
Δsan,?? 第二位及之后都是单调不降的,但是却有可能
Δ
s
a
n
,
1
>
Δ
s
a
n
,
2
\Delta sa_{n,1}>\Delta sa_{n,2}
Δsan,1?>Δsan,2?。
其实我们只需要对
j
j
j 是否大于等于
1
1
1 分类讨论处理一下就可以了:
-
f
n
,
k
=
f
n
?
1
,
k
+
s
a
n
,
0
f_{n,k}=f_{n-1,k}+sa_{n,0}
fn,k?=fn?1,k?+san,0?。
-
f
n
,
k
=
min
?
i
+
j
=
k
,
j
≥
1
(
f
n
?
1
,
i
+
s
a
n
,
j
)
f_{n,k}=\min\limits_{i+j=k,j\geq 1}(f_{n-1,i}+sa_{n,j})
fn,k?=i+j=k,j≥1min?(fn?1,i?+san,j?),在定义域
j
≥
1
j\geq 1
j≥1 的情况下
s
a
n
,
j
sa_{n,j}
san,j? 的差分数组是单调不降的。
于是我们就可以做到
O
(
n
k
log
?
k
)
O(nk\log k)
O(nklogk) 了!
接着是第二个神仙的部分。
记
C
1
C_1
C1? 为商店全集,我们考虑将所有商店按
s
a
i
,
1
sa_{i,1}
sai,1? 排序,取出前
k
k
k 小,记为
S
1
S_1
S1?,显然
S
1
S_1
S1? 之外的商店都不可能取恰好
1
1
1 个物品,因为根据鸽巢原理此时
S
1
S_1
S1? 中肯定有一个商店为空(一个都没取),那么我们换成
S
1
S_1
S1? 中那个空的商店取
1
1
1 个肯定更优。
接着再考虑剩下的商店
C
2
=
C
1
?
S
1
C_2=C_1-S_1
C2?=C1??S1?,我们已经知道了它们不可能取恰好
1
1
1 个。我们再将
C
2
C_2
C2? 中的商店按
s
a
i
,
2
sa_{i,2}
sai,2? 排序,取出前
?
k
2
?
\lfloor\frac{k}{2}\rfloor
?2k?? 小,记为
S
2
S_2
S2?,发现
C
2
C_2
C2? 中
S
2
S_2
S2? 之外的商店也不可能取恰好
2
2
2 个物品,否则
S
2
S_2
S2? 中肯定有一个商店为空(注意
C
2
C_2
C2? 中商店要么取
0
0
0 个,要么取
2
2
2 个或以上,所以根据鸽巢原理
S
2
S_2
S2? 中肯定有一个商店为空),那么我们换成
S
2
S_2
S2? 中那个空的商店取
2
2
2 个肯定更优。
接着再考虑剩下的商店
C
3
=
C
2
?
S
2
C_3=C_2-S_2
C3?=C2??S2?,我们已经知道了它们不可能取恰好
1
1
1 或
2
2
2 个,……。我们一直像这样排除下去,最后会得到若干个商店取的物品数量不能是
1
,
2
,
?
?
,
k
1,2,\cdots,k
1,2,?,k 中的任意一个,它们就再也不可能被选,它们就被排除了。而剩下未被排除的商店共有
O
(
k
log
?
k
)
O(k\log k)
O(klogk) 个。注意过程中我们并没有得到
S
i
S_i
Si? 中的商店只能恰好取
i
i
i 个物品之类的结论。
于是商店的总数缩减到了
O
(
k
log
?
k
)
O(k\log k)
O(klogk) 个,时间复杂度降为
O
(
k
2
log
?
2
k
)
O(k^2\log ^2k)
O(k2log2k)。预处理的部分视实现可以做到
O
(
n
log
?
n
)
O(n\log n)
O(nlogn)、
O
(
n
log
?
k
)
O(n\log k)
O(nlogk) 或
O
(
n
)
O(n)
O(n)。
#include<bits/stdc++.h>
#define K 1010
#define N 2000010
#define ll long long
#define LNF 0x7ffffffffffffff
#define fi first
#define se second
#define pli pair<ll,int>
#define mk(a,b) make_pair(a,b)
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
int n,k;
bool vis[N];
ll f[K],g[K],h[K];
vector<ll>sa[N];
vector<pli>v[K];
void solve(int ql,int qr,int vl,int vr)
{
if(ql>qr) return;
int mid=(ql+qr)>>1;
h[mid]=LNF;
int pos=-1;
for(int i=vl;i<=vr;i++)
{
if(i<=k&&0<=mid-i&&mid-i<=k)
{
ll tmp=f[i]+g[mid-i];
if(tmp<h[mid]) h[mid]=tmp,pos=i;
}
}
assert(pos!=-1);
solve(ql,mid-1,vl,pos);
solve(mid+1,qr,pos,vr);
}
int main()
{
n=read(),k=read();
for(int i=1;i<=n;i++)
{
int m=read();
sa[i].resize(min(m+1,k+1));
sa[i][0]=read();
for(int j=1;j<=m;j++)
{
if(j<=k)
{
sa[i][j]=sa[i][j-1]+read();
v[j].push_back(mk(sa[i][j],i));
}
else read();
}
}
for(int i=1;i<=k;i++)
{
sort(v[i].begin(),v[i].end());
int s=k/i;
for(int j=0,tot=0;j<(int)v[i].size()&&tot<s;j++)
{
if(!vis[v[i][j].se])
{
vis[v[i][j].se]=1;
tot++;
}
}
}
for(int i=1;i<=k;i++) f[i]=LNF/10;
for(int i=1;i<=n;i++)
{
if(vis[i])
{
for(int j=0;j<(int)sa[i].size();j++) g[j]=sa[i][j]-sa[i][0];
for(int j=(int)sa[i].size();j<=k;j++) g[j]=LNF/10;
solve(0,k,0,k);
for(int j=0;j<=k;j++) f[j]=min(f[j],sa[i][0]+h[j]);
}
}
for(int i=1;i<=k;i++)
printf("%lld ",f[i]);
return 0;
}
|