zoukankan      html  css  js  c++  java
  • @codeforces


    @description@

    已知 a 序列,并给定以下关系:

    [egin{cases} f(1, j) = a_j & (1 le j le n) \ f(i, j) = min{f(i - 1, j), f(i - 1, j - 1)} + a_j & (2 le i le j le n) end{cases}]

    给定 m 次询问 (xi, yi),求 f(xi, yi) 的值。

    Input
    第一行一个整数 n (1 ≤ n ≤ 10^5) — a 序列的大小。
    接下来一行 n 个整数 a[1], a[2], ..., a[n] (0 ≤ a[i] ≤ 10^4)。
    接下来一行一个整数 m (1 ≤ m ≤ 10^5),询问的个数。
    接下来 m 行每行包含两个整数 xi, yi (1 ≤ xi ≤ yi ≤ n),表示求 f(xi, yi) 的值。

    Output
    输出 m 行,表示每组询问对应的答案。

    Examples
    Input
    6
    2 2 3 4 3 4
    4
    4 5
    3 4
    3 4
    2 3
    Output
    12
    9
    9
    5

    Input
    7
    1 3 2 3 4 0 2
    4
    4 5
    2 3
    1 4
    4 6
    Output
    11
    4
    3
    0

    @solution@

    考虑那个看起来像 dp 的式子 f(i, j) 的实际意义:
    从 j 开始往前选一段连续的区间 [x, j],保证 a[x] ~ a[j] 都至少被选一次,选出共 i 个数。并求这 i 个数之和的最小值。

    然后考虑分析性质。
    除了 a[x] ~ a[j] 都至少选一次这些固定的选择,其他可用的选择应该都选择 [x, j] 中的最小值。
    假如说 a[x] 不是最小值,则我们可以把 a[x] 删掉,将多出来的可用选择用于最小值。因此左端点一定为最小值。
    记 s 为 a 的前缀和,则此时代价为 s[j] - s[x-1] + (i - (j-x+1))*a[x]。
    因为左端点不为最小值时,肯定不优。所以我们问题转为求 s[j] - s[x-1] + (i - (j-x+1))*a[x] 的最小值 (j-i+1 <= x <= j)

    注意到 s[j] - s[x-1] + (i - (j-x+1))*a[x] 是一个很经典的斜率式,可以写成 (j - i)*a[x] + (b - s[j]) = (x - 1)*a[x] - s[x-1],最小化 b 的值。
    当 i, j 固定时,相当于我们每次拿一个斜率为 (j - i) 的直线去切点集 (a[x], (x - 1)*a[x] - s[x-1]) (j-i+1 <= x <= j),找最小截距。

    如果想要去维护出每次询问中区间内的凸包,虽然询问只需要 O(log) 二分一下,但是构建凸包复杂度很大。
    我们不妨考虑平衡一下复杂度:将一个询问在线段树上拆成 log 个询问,在线段树的每个结点中维护该结点对应的凸包。
    则这样询问复杂度与凸包构建的复杂度就都可以平衡到 O(nlog^2n) 了。

    @accepted code@

    #include<cstdio>
    #include<vector>
    #include<algorithm>
    using namespace std;
    #define lch (x<<1)
    #define rch (x<<1|1)
    typedef long long ll;
    const int MAXN = 100000;
    struct point{
    	ll x, y;
    	point(ll _x=0, ll _y=0):x(_x), y(_y) {}
    	friend bool operator < (point a, point b) {
    		return (a.x == b.x) ? a.y < b.y : a.x < b.x;
    	}
    };
    long double slope(point a, point b) {
    	return ((long double)(b.y - a.y)) / (b.x - a.x);
    }
    int s[MAXN + 5], a[MAXN + 5], n, m;
    int le[4*MAXN + 5], ri[4*MAXN + 5];
    vector<point>A[4*MAXN + 5], tmp;
    void build(int x, int l, int r) {
    	le[x] = l, ri[x] = r; tmp.clear();
    	for(int i=l;i<=r;i++)
    		tmp.push_back(point(a[i], -s[i-1] + (i-1)*a[i]));
    	sort(tmp.begin(), tmp.end());
    	for(int i=0;i<tmp.size();i++) {
    		if( !A[x].size() ) A[x].push_back(tmp[i]);
    		else {
    			if( tmp[i].x != A[x][A[x].size() - 1].x ) {
    				while( A[x].size() >= 2 ) {
    					int m = A[x].size();
    					if( slope(A[x][m-2], A[x][m-1]) >= slope(A[x][m-1], tmp[i]) ) A[x].pop_back();
    					else break;
    				}
    				A[x].push_back(tmp[i]);
    			}
    		}
    	}
    	if( l == r ) return ;
    	int mid = (l + r) >> 1;
    	build(lch, l, mid), build(rch, mid + 1, r);
    }
    ll query(point a, int k) {return a.y - k*a.x;}
    ll query(const vector<point>&v, int k) {
    	int l = 0, r = v.size() - 1;
    	while( l < r ) {
    		int mid = (l + r) >> 1;
    		if( slope(v[mid], v[mid + 1]) >= k ) r = mid;
    		else l = mid + 1;
    	}
    	return query(v[r], k);
    }
    ll query(int x, int l, int r, int k) {
    	if( l <= le[x] && ri[x] <= r )
    		return query(A[x], k);
    	int mid = (le[x] + ri[x]) >> 1;
    	if( r <= mid ) return query(lch, l, r, k);
    	else if( l > mid ) return query(rch, l, r, k);
    	else return min(query(lch, l, r, k), query(rch, l, r, k));
    }
    int main() {
    	scanf("%d", &n);
    	for(int i=1;i<=n;i++)
    		scanf("%d", &a[i]), s[i] = s[i-1] + a[i];
    	build(1, 1, n);
    	scanf("%d", &m);
    	for(int i=1;i<=m;i++) {
    		int x, y; scanf("%d%d", &x, &y);
    		/*int ans = inf;
    		for(int j=y;j>=y-x+1;j--)
    			ans = min(ans, int(Y(j) - (y-x)*X(j))); // b = y - kx, y = kx + b.
    		printf("%d
    ", ans + s[y]);*/
    		printf("%lld
    ", query(1, y-x+1, y, y-x) + s[y]);
    	}
    }
    

    @details@

    合并两个凸包的复杂度等价于构建凸包的复杂度,即凸包不适合进行合并。
    这使得凸包并不大适合线段树等依赖于信息合并的数据结构(本题是利用线段树拆解询问,而不是利用线段树合并凸包)。
    但某种意义上来说,凸包挺适合分块的。

  • 相关阅读:
    [SCOI2010]连续攻击游戏
    [SCOI2010]幸运数字
    SCOI2010第一场
    Asm.Def谈笑风生
    商务旅行
    [NOI2000] 单词查找树
    [HNOI2004] L语言
    于是他错误的点名开始了
    字典
    [SDOI2015]星际战争
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11693859.html
Copyright © 2011-2022 走看看