zoukankan      html  css  js  c++  java
  • 启智树提高组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])});
    	}
    }
    
  • 相关阅读:
    单片机触摸屏校准
    C中的预编译宏定义
    Android之网络摄像头
    曾经的UCOSii
    关于ST-Link下载STM32程序的使用
    关于IAR开发STM32配置
    学习C#(一)
    ESP8266使用详解--基于Lua脚本语言
    (五)Lua脚本语言入门
    (四)Lua脚本语言入门(数组遍历)
  • 原文地址:https://www.cnblogs.com/JiaZP/p/13566265.html
Copyright © 2011-2022 走看看