[IOI2000] 邮局 P10967/P4767/P6246
P10967
设在 \(1\sim i\) 装了 \(j\) 个邮局的答案 \(f_{i,j}\):\(f_{i,j}=\min\{f_{k,j-1}+w_{k+1,i}\}\),其中 \(w_{l,r}\) 为 \(l\sim r\) 有一个邮局的最小距离。
- \(w_{l,r}\) 怎么求?在中位点装邮局。那么有 \(w_{l,r}=w_{l,r-1}+x_j-x_{[(i+j)/2]}\)。其中 \(x\) 是村庄位置。进一步有 \(\mathcal{O}(1)\) 算法,\(m=[(l+r)/2],w_{l,r}=x_{m}\times (2m-l-r-1)+sum_{l+1}+sum_r-2sum_m\)。所以这个不是复杂度瓶颈。
得到了一个 \(\mathcal{O}(v^2p)\) 的算法。
P4767
\(f_{i,j}=\min\{f_{k,j-1}+w_{k+1,i}\}\) 这个方程内,状态较难优化,所以尝试优化转移。
证:\(w_{l,r}\) 符合 \(Q.I.\)。
\(w_{i,j}+w_{i',j'}\le \sum_{l=i}^j |d_l-d_z|+\sum_{l=i'}^{j'}|d_l-d_y|\)。
\(\le \sum_{l=i}^j |d_l-d_z|+\sum_{l=i'}^{j'}|d_l-d_y|+\sum_{l=j+1}^{j'}|d_l-d_z|-\sum_{l=j+1}^{j'}|d_l-d_y|\)。
\(=\sum_{l=i}^{j'}|d_l-d_z|+\sum_{l=i'}^j |d_l-d_y|=w(i,j')+w(i',j)\)。
令 \(m_{i,j}\) 为 \(f_{i,j}\) 最小决策点。
引理 \(1\):\(m_{i+1,j}\ge m_{i,j}\)。
引理 \(2\):\(m_{i,j-1}\le m_{i,j}\)。
计算 \(m_{i,j-1}\le k\le m_{i+1,j}\)。从小到大枚举 \(j\),从大到小枚举 \(i\) 即可。
时间复杂度 \(\mathcal{O}(pv)\)。
P6246
尝试优化状态!这个“邮局数量”状态让人联想起 wqs 二分。
或者感性理解:如果每建立一个邮局有一个附加的费用,费用越高你想要建立的邮局数量就会减少。所以是一个上凸包。
二分 \(mid\) 费用,就变成了:\(f_{i}=\min\{f_{k}+w_{k+1,i}+mid\}\)。这个是 1D-1D dp 方程。
\(f(i)=\min_{0\le j<i} f(j)+w(j,i)\)。(注意是 \(\min\)!)组数没有限制的分组问题。
定理:若 \(w\) 符合 \(Q.I.\),当 \(d\ge c\) 时,\(f(d)\) 的最优决策点 \(\ge f(c)\) 最优决策点。
因此:对于每个已经计算出来的 \(f_i\),去寻找它能更新的状态有哪些。在栈顶的决策起始位置判断起始位置是否决策 \(i\) 更优。如果是,则退栈,继续执行,否则,二分决策位置。
最终代码:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 5e5+5;
struct node {
ll l,r,x;
} stk[N];
ll n,m,a[N],sum[N],dp[N],top,cnt[N];
ll w(ll l,ll r){
ll mid=(l+r+1)/2;
return a[mid]*(mid+mid-l-r)+sum[l]+sum[r]-sum[mid]*2;
}
int ff(int i){
int l=stk[top].l-1,r=stk[top].r+1;
while (l+1<r){
int mid=l+r>>1;
if (dp[i]+w(i,mid)<=dp[stk[top].x]+w(stk[top].x,mid)){
r=mid;
}
else{
l=mid;
}
}
return r;
}
bool chk(ll x){
top=1;
stk[1]={1,n,0};
for (int i=1,cur=1; i<=n; i++){
dp[i]=dp[stk[cur].x]+w(stk[cur].x,i)+x;
cnt[i]=cnt[stk[cur].x]+1;
while (i<stk[top].l && dp[i]+w(i,stk[top].l)<=dp[stk[top].x]+w(stk[top].x,stk[top].l)){
top--;
}
int pos=ff(i);
stk[top].r=pos-1;
if (pos<=n){
stk[++top]={pos,n,i};
}
if (i==stk[cur].r){
cur++;
}
}
return cnt[n]>=m;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
for (int i=1; i<=n; i++){
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
ll l=-1,r=1e9;
r++;
while (l+1<r){
ll mid=l+r>>1;
if (chk(mid)){
l=mid;
}
else{
r=mid;
}
}
chk(l);
cout<<dp[n]-l*m<<"\n";
return 0;
}