1. 程式人生 > 實用技巧 >啟智樹提高組Day3 T3 pancake

啟智樹提高組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])});
	}
}