zoukankan      html  css  js  c++  java
  • [题解] bzoj 4826 hnoi 2017 影魔(线段树 + 扫描线)

    - 传送门 -

     http://www.lydsy.com/JudgeOnline/problem.php?id=4826

    # 4826: [Hnoi2017]影魔

    Time Limit: 20 Sec  Memory Limit: 512 MB
    Submit: 539  Solved: 293
    [Submit][Status][Discuss]

    Description

    影魔,奈文摩尔,据说有着一个诗人的灵魂。事实上,他吞噬的诗人灵魂早已成千上万。千百年来,他收集了各式各样的灵魂,包括诗人、牧师、帝王、乞丐、奴隶、罪人,当然,还有英雄。每一个灵魂,都有着自己的战斗力,而影魔,靠这些战斗力提升自己的攻击。奈文摩尔有 n 个灵魂,他们在影魔宽广的体内可以排成一排,从左至右标号 1 到 n。

    第 i个灵魂的战斗力为 k[i],灵魂们以点对的形式为影魔提供攻击力,对于灵魂对 i,j(i< j)来说,若不存在 k[s](i < s < j)大于 k[i]或者 k[j],则会为影魔提供 p1 的攻击力(可理解为:当 j=i+1 时,因为不存在满足 i< s < j 的 s,从而k[s]不存在,这时提供 p1 的攻击力;当 j>i+1 时,若max{k[s]|i < s < j}<=min{k[i],k[j]} ,则提供p1的攻击力); 另一种情况 ,令c为k[i+1],k[i+2],k[i+3]......k[j-1]的最大值,若 c 满足:k[i] < c < k[j],或者 k[j] < c < k[i],则会为影魔提供 p2 的攻击力,当这样的 c 不存在时,自然不会提供这 p2 的攻击力;其他情况的点对,均不会为影魔提供攻击力。影魔的挚友噬魂鬼在一天造访影魔体内时被这些灵魂吸引住了,他想知道,对于任意一段区间[a,b],1 <= a < b <= n, 位于这些区间中的灵魂对会为影魔提供多少攻击力,即考虑 所有满足a <= i < j <= b 的灵魂对 i,j 提供的攻击力之和。顺带一提,灵魂的战斗力组成一个 1 到 n 的排列:k 1 ,k[2],...,k[n]。

    Input

    第一行 n,m,p1,p2

    第二行 n 个数:k 1 ,k[2],...,k[n]

    接下来 m 行,每行两个数 a,b,表示询问区间[a,b]中的灵魂对会为影魔提供多少攻击力。

    1 <= n,m <= 200000;1 <= p1,p2 <= 1000

    Output

    共输出 m 行,每行一个答案,依次对应 m 个询问。

    Sample Input

    10 5 2 3
    7 9 5 1 3 10 6 8 2 4
    1 7
    1 9
    1 3
    5 9
    1 5

    Sample Output

    30
    39
    4
    13
    16
     

    - 题意 -

     对于一个序列A, 若点对(i, j)满足A[i], A[j] 均大于(i, j)内所有数, 该点对有一个贡献值p1, 若其中有一个满足该条件, 则该点对有一个贡献值p2, 问对于给定区间[a, b], 其中所有点对的总贡献值是多少.
     

    - 思路 -

     设 l[i] 为 i 往左数第一个大于 A[i] 的数的位置, r[i] 为 i 往右数第一个大于 A[i] 的数的位置.
     可以得到:
      1. 点对(l[i] , r[i])有 p1 的贡献.(注意这里没有考虑(i, i+1)造成的 p1 贡献, 要在最后加上)
      2. 点对(l[i], j) (i < j < r[i]) 有 p2 的贡献.
      3. 点对(j, r[i]) (l[i] < j < i) 有 p2 的贡献.
     我们试着把点对转移到平面上去.
     点对的左端点指横坐标, 右端点指纵坐标, 这样就将以上有贡献的点对转化成了平面上有权值的点, 第 2 组点对会形成若干条与 y 轴平行的线, 第 3 组同理, 与 x 轴平行.
     对于询问[a , b], 我们要找以(a , b), (n, 1)为对角线的矩形内(包括边界)的点(线)的权值和.(a 表示点对的左端, 故可以增大, b 可以减小, 但是注意当纵坐标大于横坐标时实际上是没有点存在的).
     这就可以用扫描线了, 扫一遍横线再扫一遍竖线, 第一组点对(也就是平面上的点)试做线段随便加到 2 或 3 组去.
     不同于平常的扫描线, (以 x 轴平行的线为例, 我们将同一高度的线存在一起, 然后再将同一高度的询问(矩形的高)也存起来, 这样就可以再处理完一个高度的线之后马上处理该高度的询问), 并且注意扫的方向.
     
     细节见代码.
     
     PS:
     树状数组快很多, 这里用到了树状数组的区间修改区间查询, 代码贴在最后.
     

    - 代码 -

    
    #include<cstdio>
    #include<algorithm>
    #include<vector>
    #define s second
    #define ls rt << 1
    #define rs rt << 1 | 1
    using namespace std;
    
    typedef long long ll;
    const int M = 2e5 + 5;
    
    int K[M], L[M], R[M];
    int QL[M], QR[M];
    ll ADD[M << 2], SUM[M << 2];
    ll ANS[M];
    int n, m, p1, p2;
    int a, b, x, y;
    int cntx, cnty;
    
    vector<int> QX[M], QY[M], XX[M], YY[M];
    pair<int, int> pai, PA[M];
    
    struct edge {
    	int a, b1, b2, c;
    }WX[M<<1], WY[M];
    
    template <typename T> void read(T &x) {
    	x = 0; int f = 1; char ch = getchar();
    	while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
    	while (ch >= '0' && ch <= '9') { x = x*10 + ch - '0'; ch = getchar(); }
    	x *= f;
    }
    
    void init() {
    	int tmp = 1;
    	L[1] = 0;
    	PA[1] = make_pair(K[1], 1);
    	for (int i = 2; i <= n; ++i) {
    		pai = make_pair(K[i], i);
    		while (pai > PA[tmp] && tmp) tmp--;
    		if (!tmp) L[i] = 0;
    		else L[i] = PA[tmp].s;
    		PA[++tmp] = pai;
    	}
    	tmp = 1;
    	R[n] = n + 1;
    	PA[1] = make_pair(K[n], n);
    	for (int i = n - 1; i >= 1; --i) {
    		pai = make_pair(K[i], i);
    		while (pai > PA[tmp] && tmp) tmp--;
    		if (!tmp) R[i] = n + 1;
    		else R[i] = PA[tmp].s;
    		PA[++tmp] = pai;
    	} //先用单调栈找l[i], r[i]
    }
    
    void pd(int rt, int l, int r) {
    	if (ADD[rt]) {
    		int tmp = ADD[rt], mid = l + r >> 1;
    		ADD[ls] += tmp;
    		ADD[rs] += tmp;
    		SUM[ls] += tmp * (mid - l + 1);
    		SUM[rs] += tmp * (r - mid);
    		ADD[rt] = 0;
    	}
    }//标记下传
    
    void add(int rt, int l, int r, int x, int y, int c) {
    	if (x <= l && r <= y) {
    		ADD[rt] += c;
    		SUM[rt] += c * (r - l + 1);
    		return;
    	}
    	pd(rt, l, r);
    	int mid = l + r >> 1;
    	if (x <= mid) add(ls, l, mid, x, y, c);
    	if (mid < y) add(rs, mid + 1, r, x, y, c);
    	SUM[rt] = SUM[ls] + SUM[rs];
    }
    
    ll query(int rt, int l, int r, int x, int y) {
    	if (x <= l && r <= y)
    		return SUM[rt];
    	pd(rt, l, r);
    	ll ans = 0;
    	int mid = l + r >> 1;
    	if (x <= mid) ans += query(ls, l, mid, x, y);
    	if (mid < y) ans += query(rs, mid + 1, r, x, y);
    	return ans;
    }
    
    void rebuild(int rt, int l, int r) {
    	ADD[rt] = SUM[rt] = 0;
    	if (l == r) return;
    	int mid = l + r >> 1;
    	rebuild(ls, l, mid);
    	rebuild(rs, mid + 1, r);
    }
    
    int main() {
    	read(n), read(m), read(p1), read(p2);
    	for (int i = 1; i <= n; ++i)
    		read(K[i]);
    	init();
    	for (int i = 1; i <= m; ++i) {
    		read(QL[i]), read(QR[i]);
    		QX[QR[i]].push_back(i);
    		QY[QL[i]].push_back(i);
    	}//记录询问, 对于每个询问要扫一遍横线, 扫一遍竖线, 故要在横轴数轴上各标记一次
    	for (int i = 1; i <= n; ++i) {
    		if (L[i] > 0 && R[i] < n + 1) {
    			WX[++cntx].a = R[i], WX[cntx].c = p1;
    			WX[cntx].b1 = WX[cntx].b2 = L[i];
    			XX[R[i]].push_back(cntx);
    		}//记录贡献为 p1 的点, 我将它归到了横线中
    		if (L[i] > 0 && i + 1 < R[i]) {
    			WY[++cnty].a = L[i], WY[cnty].c = p2;
    			WY[cnty].b1 = i + 1, WY[cnty].b2 = R[i] - 1;
    			YY[L[i]].push_back(cnty);
    		}//记录竖线
    		if (R[i] < n + 1 && L[i] < i - 1) {
    			WX[++cntx].a = R[i], WX[cntx].c = p2;
    			WX[cntx].b1 = L[i] + 1, WX[cntx].b2 = i - 1;
    			XX[R[i]].push_back(cntx);
    		}//记录横线
    	}
    	for (int i = 1; i <= n; ++i) {
    		for (int j = 0; j < XX[i].size(); ++j) {
    			int tmp = XX[i][j];
    			add(1, 1, n, WX[tmp].b1, WX[tmp].b2, WX[tmp].c);
    		}//添加线
    		for (int j = 0; j < QX[i].size(); ++j) {
    			ANS[QX[i][j]] += query(1, 1, n, QL[QX[i][j]], n);
    		}//同一高度上的询问
    	}//横扫
    	rebuild(1, 1, n);
    	for (int i = n; i >= 1; --i) {
    		for (int j = 0; j < YY[i].size(); ++j) {
    			int tmp = YY[i][j];
    			add(1, 1, n, WY[tmp].b1, WY[tmp].b2, WY[tmp].c);
    		}
    		for (int j = 0; j < QY[i].size(); ++j) {
    			ANS[QY[i][j]] += query(1, 1, n, 1, QR[QY[i][j]]);
    		}
    	}//竖扫
    	for (int i = 1; i <= m; ++i)
    		printf("%lld
    ", ANS[i] + (QR[i] - QL[i]) * p1);//考虑[i, i+1]造成的 p1 贡献
    	return 0;
    }
    

     
     树状数组版:
     

    
    #include<cstdio>
    #include<algorithm>
    #include<vector>
    #include<cstring>
    #define s second
    #define ls rt << 1
    #define rs rt << 1 | 1
    using namespace std;
    
    typedef long long ll;
    const int M = 2e5 + 5;
    
    int K[M], L[M], R[M];
    int QL[M], QR[M];
    ll B[M], C[M];
    ll ANS[M];
    int n, m, p1, p2;
    int a, b, x, y;
    int cntx, cnty;
    
    vector<int> QX[M], QY[M], XX[M], YY[M];
    pair<int, int> pai, PA[M];
    
    struct edge {
    	int a, b1, b2, c;
    }WX[M<<1], WY[M];
    
    template <typename T> void read(T &x) {
    	x = 0; int f = 1; char ch = getchar();
    	while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
    	while (ch >= '0' && ch <= '9') { x = x*10 + ch - '0'; ch = getchar(); }
    	x *= f;
    }
    
    void init() {
    	int tmp = 1;
    	L[1] = 0;
    	PA[1] = make_pair(K[1], 1);
    	for (int i = 2; i <= n; ++i) {
    		pai = make_pair(K[i], i);
    		while (pai > PA[tmp] && tmp) tmp--;
    		if (!tmp) L[i] = 0;
    		else L[i] = PA[tmp].s;
    		PA[++tmp] = pai;
    	}
    	tmp = 1;
    	R[n] = n + 1;
    	PA[1] = make_pair(K[n], n);
    	for (int i = n - 1; i >= 1; --i) {
    		pai = make_pair(K[i], i);
    		while (pai > PA[tmp] && tmp) tmp--;
    		if (!tmp) R[i] = n + 1;
    		else R[i] = PA[tmp].s;
    		PA[++tmp] = pai;
    	}
    }
    
    int lowbit(int x) { return x&(-x); }
    
    void add(int rt, int c) {
    	for (int i = rt; i <= n; i += lowbit(i)) {
    		B[i] += c;
    		C[i] += c * rt;
    	}
    }
    
    ll query(int rt, int k) {
    	ll ans = 0;
    	for (int i = rt; i > 0; i -= lowbit(i)) {
    		ans += k * B[i];
    		ans -= C[i];
    	}
    	return ans;
    }
    
    int main() {
    	read(n), read(m), read(p1), read(p2);
    	for (int i = 1; i <= n; ++i)
    		read(K[i]);
    	init();
    	for (int i = 1; i <= m; ++i) {
    		read(QL[i]), read(QR[i]);
    		QX[QR[i]].push_back(i);
    		QY[QL[i]].push_back(i);
    	}
    	for (int i = 1; i <= n; ++i) {
    		if (L[i] > 0 && R[i] < n + 1) {
    			WX[++cntx].a = R[i], WX[cntx].c = p1;
    			WX[cntx].b1 = WX[cntx].b2 = L[i];
    			XX[R[i]].push_back(cntx);
    		}
    		if (L[i] > 0 && i + 1 < R[i]) {
    			WY[++cnty].a = L[i], WY[cnty].c = p2;
    			WY[cnty].b1 = i + 1, WY[cnty].b2 = R[i] - 1;
    			YY[L[i]].push_back(cnty);
    		}
    		if (R[i] < n + 1 && L[i] < i - 1) {
    			WX[++cntx].a = R[i], WX[cntx].c = p2;
    			WX[cntx].b1 = L[i] + 1, WX[cntx].b2 = i - 1;
    			XX[R[i]].push_back(cntx);
    		}
    	}
    	for (int i = 1; i <= n; ++i) {
    		for (int j = 0; j < XX[i].size(); ++j) {
    			int tmp = XX[i][j];
    			add(WX[tmp].b1, WX[tmp].c);
    			add(WX[tmp].b2 + 1, -WX[tmp].c);
    		}
    		for (int j = 0; j < QX[i].size(); ++j) {
    			int tmp = QX[i][j];
    			ANS[tmp] += query(n, n + 1);
    			ANS[tmp] -= query(QL[tmp] - 1, QL[tmp]);
    		}
    	}
    	memset(B, 0, sizeof B);
    	memset(C, 0, sizeof C);
    	for (int i = n; i >= 1; --i) {
    		for (int j = 0; j < YY[i].size(); ++j) {
    			int tmp = YY[i][j];
    			add(WY[tmp].b1, WY[tmp].c);
    			add(WY[tmp].b2 + 1, -WY[tmp].c);
    		}
    		for (int j = 0; j < QY[i].size(); ++j) {
    			int tmp = QY[i][j];
    			ANS[tmp] += query(QR[tmp], QR[tmp] + 1);
    			ANS[tmp] -= B[1];
    		}
    	}
    	for (int i = 1; i <= m; ++i)
    		printf("%lld
    ", ANS[i] + (QR[i] - QL[i]) * p1);
    	return 0;
    }
    
  • 相关阅读:
    浅析 KMP
    【GOJ 3049】玩具
    较详细的gdb入门教程
    【GOJ 2963】记者
    【GOJ 2961】数数
    GF OIer's Talk 维护笔记
    Linux 中 UFW 的使用
    开源是什么?能吃吗?
    个人介绍
    NOIP2020 爆零记
  • 原文地址:https://www.cnblogs.com/Anding-16/p/7327828.html
Copyright © 2011-2022 走看看