启智树提高组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])});
	}
}
posted @ 2020-08-26 17:10  JiaZP  阅读(169)  评论(0编辑  收藏  举报