BOXES 题解
Transparent · · 题解
提供一种比费用流更优秀的 dp 做法,但是不能通过 Hard Version。
这个做法可以解决:给定一个环和环上每个位置初始的元素个数
考虑断开一条边成为链的情况,通过枚举断开的边的传递情况就可以解决环的问题。这时第一个位置只能向后传递。于是可以 dp,设
显然可以单调队列做到
复杂度还不优秀,考虑优化转移的过程。令
- 令
f(x)=\min\limits_{i=x}^{x+a}f(i) - 令
f(x) 沿x 轴方向左右平移a 个单位。 - 令
f(x)=f(x)+|x|
首先发现,在
考虑用对顶堆实现,两个堆存放斜率的转折点,其中一个维护斜率大于
第一个操作即为:在斜率为
第二个操作直接对上升段和下降段一起打 tag 即可,对最值无影响。
第三个操作中,由于
-
-
(即下降段最后一个转折点)会在修改后不再属于下降段,因为现在在这个点,斜率是由 $0$ 变为 $1$,需要从下降段移除并加入上升段,显然,最值需要加上下降段末尾到 $0$ 的距离。 -
最后根据最低点的值,沿斜率转折点依次移到
至此,一次 dp 的复杂度可以做到
现在还需要确定断开的那条边上的传递情况,如果枚举,就可以得到一个
设
首先,将每次移动次数拓展到实数,答案不变。感性理解是要取得最值时,一定有至少一个位置恰为
然后假设有两个合法方案
于是可以在最外层套三分,最终复杂度为
#include <ext/pb_ds/priority_queue.hpp>
#include<bits/stdc++.h>
using namespace std;
constexpr int MAXN=2e5+10;
int n,a[MAXN];
int calc(int x) {
int tagl=0,tagr=0,val=0;
__gnu_pbds::priority_queue<int,less<int>,__gnu_pbds::pairing_heap_tag>ql;
__gnu_pbds::priority_queue<int,greater<int>,__gnu_pbds::pairing_heap_tag>qr;
for(int i=1;i<=n;++i) ql.push(x),qr.push(x);
for(int i=1;i<=n;++i) {
tagl+=a[i]-1;tagr+=a[i];
int lp=ql.top()+tagl,rp=qr.top()+tagr;
if(lp<=0&&0<=rp) {
ql.push(-tagl);qr.push(-tagr);
} else if(lp>0) {
val+=lp;ql.pop();ql.push(-tagl),ql.push(-tagl);qr.push(lp-tagr);
} else {
val-=rp;ql.push(rp-tagl);qr.pop();qr.push(-tagr);qr.push(-tagr);
}
}
while(!ql.empty()) {
int lp=ql.top()+tagl;ql.pop();
if(lp<=x) break;
val+=lp-x;
}
while(!qr.empty()) {
int rp=qr.top()+tagr;qr.pop();
if(rp>=x) break;
val+=x-rp;
}
return val;
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
int suma=0;
for(int i=1;i<=n;++i) suma+=a[i];
int L=-suma,R=suma,res=0;
while(L<=R) {
int mid=(L+R)>>1;
if(calc(mid)<calc(mid+1)) {
res=mid,R=mid-1;
} else L=mid+1;
}
cout<<calc(res)<<endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
int T=0;cin>>T;
while(T--) solve();
cout<<flush;
return 0;
}