启智树提高组Day3 T3 pancake
现有 \(n\) 个线段,第 \(i\) 个线段长度为 \(l_i\),现最多可以切 \(k\) 刀,只能在整数位置切。要求最小化切完后所有线段的长度的平方和。又有 \(q\) 次操作,每次将 \(k\) 加一或减一,或者新加一段长为 \(l_i\) 的线段。每次问最小的平方和。\(n,q,l_i,k \le 10^5\)。保证答案不爆 long long
首先 \(nqk\) 的背包DP就不说了,反正正解和这没啥关系。
注意不到平方和关于切的刀数是个下凸函数,或者说平方和的减少量这个东西关于切的刀数 \(k\) 是个减函数。于是可以费用流。
考虑一种网络流模型,将代价转化为 $l_i^2 - $ 切 \(k\) 刀的收益。将收益拆成若干份,如果多给它一刀就会多获得一些收益,跑最大费用可行流即可。正确性在于代价函数是下凸函数。这个费用流模型的优势在于能更好地应对 \(k\) 的微小变化。
考虑模拟费用流。用一个堆维护每个段当前能扩展出来的差分,再用一个堆来维护已经选择的那些差分。先捡收益大的 \(k\) 个差分作为初始答案。如果 \(k\) 加一,那么从第一个堆中找出最大的那个扔到第二个堆中并计入答案;如果 \(k\) 减一,那么从第二个堆中选出最小的那个,放弃掉它。
对于新加入一个段,我们可以直接暴力做,一直到它能扩展出来的差分小于已经选择的最小的差分。我目前不会证复杂度(据说总共 \(O(nlogn)\) 次),但是能够感性理解如果加一个巨大的段而之前都非常小,我们会操作好多次,但是会把平均值拉大,以后就很难再操作那么多次了。复杂度正确的主要原因是没有删除线段的操作。
总复杂度\(O(nlog^2n)\)。
关键代码:
struct node {
int cur;
ll val;
bool operator <(const node a) const {
return val < a.val || (val == a.val && cur < a.cur);
}
};
inline ll calc(int x, int k) {
int rst = x % k;
int xia = x / k;
int shang = (x + k - 1) / k;
ll res= 1ll * xia * xia * (k - rst) + 1ll * shang * shang * rst;
return res;
}
inline ll Calc(int x, int k) {
ll res = calc(h[x], k) - calc(h[x], k + 1);
return res;
}
multiset<node> st, ts;
inline void init() {
for (register int i = 1; i <= n; ++i)
ts.insert((node){i, Calc(i, cnt[i])});
for (register int i = 1; i <= k; ++i) {
node nd = *(--ts.end());
ts.erase(nd);
st.insert(nd);
res += nd.val;
ts.insert((node){nd.cur, Calc(nd.cur, ++cnt[nd.cur])});
}
}
inline void add() {
++k;
node nd = *(--ts.end());
ts.erase(nd);
st.insert(nd);
res += nd.val;
ts.insert((node){nd.cur, Calc(nd.cur, ++cnt[nd.cur])});
}
inline void del() {
--k;
node nd = *(st.begin());
res -= nd.val;
st.erase(st.begin());
ts.erase((node){nd.cur, Calc(nd.cur, cnt[nd.cur])});
--cnt[nd.cur];
ts.insert((node){nd.cur, Calc(nd.cur, cnt[nd.cur])});
}
inline void ins(int i) {
ts.insert((node){i, Calc(i, cnt[i])});
while ((*(--ts.end())).val > (*st.begin()).val) {
node nd = (*st.begin());
st.erase(st.begin());
st.insert(*(--ts.end())), ts.erase(--ts.end()), res += Calc(i, cnt[i]),
ts.insert((node){i, Calc(i, ++cnt[i])});
res -= nd.val;
ts.erase(ts.find((node){nd.cur, Calc(nd.cur, cnt[nd.cur])}));
--cnt[nd.cur];
ts.insert((node){nd.cur, Calc(nd.cur, cnt[nd.cur])});
}
}