• 【bzoj4540】[Hnoi2016]序列 单调栈+离线+扫描线+树状数组区间修改区间查询


    题目描述

    给出一个序列,多次询问一个区间的所有子区间最小值之和。

    输入

    输入文件的第一行包含两个整数n和q,分别代表序列长度和询问数。接下来一行,包含n个整数,以空格隔开,第i个整数为ai,即序列第i个元素的值。接下来q行,每行包含两个整数l和r,代表一次询问。

    输出

    对于每次询问,输出一行,代表询问的答案。

    样例输入

    5 5
    5 2 4 1 3
    1 5
    1 3
    2 4
    3 5
    2 5

    样例输出

    28
    17
    11
    11
    17


    题解

    单调栈+离线+扫描线+树状数组区间修改区间查询

    首先把使用单调栈找出每个数左边第一个大于等于它的数的位置 $lp[i]$ 和右边第一个大于它的数的位置 $rp[i]$ 。

    然后每个数的贡献为:左端点在 $[lp[i]+1,i]$ ,右端点在 $[i,rp[i]-1]$ 的所有区间。

    如果把区间看作二维平面上的点的话,每个数的贡献相当于是一个矩形,查询范围也是一个矩形。因此问题转化为矩形加、查询矩形和。

    可以使用树状数组区间修改来维护,具体方法可以参考 【bzoj3132】上帝造题的七分钟 。

    然而本题坐标范围较大,不能直接上二维树状数组,需要使用扫描线去掉一维,然后使用树状数组解决。方法和那道题一样,维护 $sum v_i,sum x_iv_i,sum y_iv_i,sum x_iy_iv_i$ 即可。

    因此离线处理,把每个修改矩形、询问矩形都拆成4个点,放到一起排序,然后扫一遍统计答案即可。(一个小技巧:由于区间左端点一定小于等于右端点,因此整个平面只有横坐标小于等于纵坐标的才有意义,因此可以只把询问矩形拆成两个点)

    时间复杂度 $O(nlog n)$

    #include <cstdio>
    #include <cctype>
    #include <cstring>
    #include <algorithm>
    #define N 100010
    using namespace std;
    typedef long long ll;
    int n , a[N] , sta[N] , top , lp[N] , rp[N] , tot;
    ll ans[N];
    struct bit
    {
    	ll v[N];
    	inline void add(int x , ll a)
    	{
    		int i;
    		for(i = x ; i <= n ; i += i & -i) v[i] += a;
    	}
    	inline ll query(int x)
    	{
    		int i;
    		ll ans = 0;
    		for(i = x ; i ; i -= i & -i) ans += v[i];
    		return ans;
    	}
    }A , B , C , D;
    struct data
    {
    	int x , y , opt , c;
    	data() {}
    	data(int p , int q , int r , int s) {x = p , y = q , opt = r , c = s;}
    	bool operator<(const data &a)const {return y == a.y ? !opt && a.opt : y < a.y;}
    }p[N * 6];
    inline void modify(int x , int y , ll a)
    {
    	A.add(x , a) , B.add(x , a * x) , C.add(x , a * y) , D.add(x , a * x * y);
    }
    inline ll solve(int x , int y)
    {
    	return A.query(x) * (x + 1) * (y + 1) - B.query(x) * (y + 1) - C.query(x) * (x + 1) + D.query(x);
    }
    inline int read()
    {
    	int ret = 0 , f = 0; char ch = getchar();
    	while(!isdigit(ch)) f |= (ch == '-') , ch = getchar();
    	while(isdigit(ch)) ret = ((ret + (ret << 2)) << 1) + (ch ^ '0') , ch = getchar();
    	return f ? -ret : ret;
    }
    int main()
    {
    	n = read();
    	int m = read() , i , l , r;
    	for(i = 1 ; i <= n ; i ++ ) a[i] = read();
    	top = 0 , sta[0] = 0;
    	for(i = 1 ; i <= n ; i ++ )
    	{
    		while(top && a[i] < a[sta[top]]) top -- ;
    		lp[i] = sta[top] + 1 , sta[++top] = i;
    	}
    	top = 0 , sta[0] = n + 1;
    	for(i = n ; i ; i -- )
    	{
    		while(top && a[i] <= a[sta[top]]) top -- ;
    		rp[i] = sta[top] - 1 , sta[++top] = i;
    	}
    	for(i = 1 ; i <= n ; i ++ )
    	{
    		p[++tot] = data(lp[i] , i , 0 , a[i]);
    		p[++tot] = data(i + 1 , i , 0 , -a[i]);
    		p[++tot] = data(lp[i] , rp[i] + 1 , 0 , -a[i]);
    		p[++tot] = data(i + 1 , rp[i] + 1 , 0 , a[i]);
    	}
    	for(i = 1 ; i <= m ; i ++ )
    	{
    		l = read() - 1 , r = read();
    		p[++tot] = data(r , r , 1 , i);
    		p[++tot] = data(l , r , -1 , i);
    	}
    	sort(p + 1 , p + tot + 1);
    	for(i = 1 ; i <= tot ; i ++ )
    	{
    		if(p[i].opt) ans[p[i].c] += p[i].opt * solve(p[i].x , p[i].y);
    		else modify(p[i].x , p[i].y , p[i].c);
    	}
    	for(i = 1 ; i <= m ; i ++ ) printf("%lld
    " , ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    hadoop无法启动常见原因
    mahout版本兼容问题
    递归打印字符串
    斐波那契数列
    int('x', base)中的base参数
    打印的特殊符号
    位置参数,默认参数,非关键字可变长参数,关键字可变长参数
    try...except包含try...finally方法
    7.9 未解答
    对集合应用符号 | & ^
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/7894480.html
Copyright © 2020-2023  润新知