首页 > 其他分享 >洛谷 P9912 [COCI 2023/2024 #2] Zatopljenje 题解

洛谷 P9912 [COCI 2023/2024 #2] Zatopljenje 题解

时间:2024-02-15 18:22:06浏览次数:24  
标签:洛谷 trl idx int 题解 tree mid 2024 trr

首先发现区间中的个数等于 \(\texttt{高度大于 x 的位置的个数} - \texttt{连续两个位置都是大于 x 的位置的个数}\)。具体令 \(H_i = \min(h_i, h_{i+1}) (i \in [1, n-1])\),那么对于一次询问答案 \(ans=\sum\limits_{i=l}^{r}[h_i > x] - \sum\limits_{i=l}^{r-1}[H_i > x]\),其中 \([a > b]\) 表示当 \(a > b\) 时为 \(1\),反之为 \(0\)。特别地,对于 \(l=r\) 的情况,答案就是 \([h_i > x]\)。

证明:一个岛屿一定是由一段连续的高度大于 \(x\) 的位置组成,那么这一段连续两个位置都是大于 \(x\) 的位置的个数正好是这一段区间的长度减一(可以想象这一段区间有多少个间隔,显然是区间长度减一),那么两者相减就是 \(1\) ,求和之后就算出了 \(1\) 的个数,也就是岛屿的个数。证毕。

很明显,原问题成了两个数组中区间内大于某个数的个数,具体来讲,就是 \(h_l \sim h_r\) 大于 \(x\) 的个数减去 \(H_l \sim H_{r-1}\) 大于 \(x\) 的个数。使用分块是个做法,但是由于 不能过这道题 我们要学习更优的 \(\log\) 的算法,有以下两种解法。

离线使用线段树

考虑对原问题离线,将 \([l, r]\) 的询问拆成 \([1, r]\) 的询问减去 \([1, l-1]\) 的询问,这样从左到右扫一遍,同时维护一个数据结构能快速求得目前比 \(x\) 大的个数。离散化使用树状数组是一种方法,或者直接使用权值线段树。

时间复杂度为 \(\Theta((n+q)\log n)\) 以及一个线段树不小的常数?

具体实现如下:

#include <cstdio>
#include <vector>
using namespace std;
int n, q, h[200010], H[200010], ans[200010];
struct node{ int f, idx, x; };
vector<node> qry1[200010], qry2[200010];
const int inf = 1000000000;
struct Segment_Tree{
	struct node{ int lson, rson, sum; };
	node tree[1000010 << 2];
	int tot, root;
	void pushup(int idx){ tree[idx].sum = tree[tree[idx].lson].sum + tree[tree[idx].rson].sum; }
	int query(int &idx, int trl, int trr, int l, int r){
		if (!idx || trl > r || trr < l) return 0;
		if (l <= trl && trr <= r) return tree[idx].sum;
		int mid = (trl + trr) >> 1;
		return query(tree[idx].lson, trl, mid, l, r) + query(tree[idx].rson, mid + 1, trr, l, r);
	}
	void modify(int &idx, int trl, int trr, int p, int v){
		if (trl > p || trr < p) return;
		if (!idx) idx = ++tot, tree[idx] = {0, 0, 0};
		if (trl == trr) return tree[idx].sum += v, void();
		int mid = (trl + trr) >> 1; modify(tree[idx].lson, trl, mid, p, v), modify(tree[idx].rson, mid + 1, trr, p, v), pushup(idx);
	} void clear(){ tot = 0, root = 0; }
} yzh;
signed main(){
	scanf("%d%d", &n, &q);
	for (int i=1;i<=n;++i) scanf("%d", &h[i]), H[i-1] = min(h[i-1], h[i]);
	for (int i=1;i<=q;++i){
		int l, r, x; scanf("%d%d%d", &l, &r, &x);
		if (l == r) ans[i] = h[l] > x;
		else {
			qry1[r].push_back({1, i, x}), qry1[l-1].push_back({-1, i, x});
			qry2[l-1].push_back({1, i, x}), qry2[r-1].push_back({-1, i, x});
		}
	} for (int i=1;i<=n;++i){
		yzh.modify(yzh.root, 0, inf, h[i], 1);
		for (auto [f, idx, x]:qry1[i]) ans[idx] += f * yzh.query(yzh.root, 0, inf, x + 1, inf);
	} yzh.clear();
	for (int i=1;i<=n-1;++i){
		yzh.modify(yzh.root, 0, inf, H[i], 1);
		for (auto [f, idx, x]:qry2[i]) ans[idx] += f * yzh.query(yzh.root, 0, inf, x + 1, inf);
	} for (int i=1;i<=q;++i) printf("%d\n", ans[i]);
	return 0;
}

直接使用主席树

其实可以考虑使用可持久化的数据结构(主席树),这样可以在线解决,万一题目强制在线,那么离线的全都挂掉了。剩下的就是板子了。具体地,我们讲所有高度离散化,主席树里面存值域区间出现的数字个数。对于询问,我们在离散化之前所有高度中找到最后一个小于等于 \(x\) 的高度(由于离散化要先排序,这一步可以用 upper_bound 二分,对时间复杂度没有影响),令这个高度为 \(h_0\),那么就查询 \(l \sim r\) 中大于 \(h_0\) 的个数即可。

时间复杂度:\(\Theta((n+q)\log n)\)。

放上代码。

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

int n, q, h[200010], real[200010], V;

struct Yzh_Is_The_President{
	struct node{
		int lson, rson;
		int val;
	} tree[200010 * 4 + 200010 * 20];
	int root[200010 * 4 + 200010 * 20], tot;
	void pushup(int idx){
		tree[idx].val = tree[tree[idx].lson].val + tree[tree[idx].rson].val;
	}
	void build(int &idx, int l, int r){
		tree[idx = ++tot] = {0, 0, 0};
		if (l == r) return;
		int mid = (l + r) >> 1;
		build(tree[idx].lson, l, mid), build(tree[idx].rson, mid + 1, r);
	}
	void modify(int &idx, int oidx, int trl, int trr, int p, int v){
		if (trl > p || trr < p) return;
		tree[idx = ++tot] = tree[oidx];
		if (trl == trr) return tree[idx].val += v, void();
		int mid = (trl + trr) >> 1;
		modify(tree[idx].lson, tree[oidx].lson, trl, mid, p, v);
		modify(tree[idx].rson, tree[oidx].rson, mid + 1, trr, p, v);
		pushup(idx);
	}
	int query(int lidx, int ridx, int trl, int trr, int l, int r){
		if (trl > r || trr < l) return 0;
		if (l <= trl && trr <= r) return tree[ridx].val - tree[lidx].val;
		int mid = (trl + trr) >> 1;
		return query(tree[lidx].lson, tree[ridx].lson, trl, mid, l, r) + 
			   query(tree[lidx].rson, tree[ridx].rson, mid + 1, trr, l, r);
	}
} xym, yzh;

signed main() {
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; ++i) scanf("%d", &h[i]), real[i] = h[i];
    sort(real + 1, real + n + 1);
    V = unique(real + 1, real + n + 1) - real - 1;
    for (int i = 1; i <= n; ++i) h[i] = lower_bound(real + 1, real + V + 1, h[i]) - real;
    
    xym.build(xym.root[0], 1, V), yzh.build(yzh.root[0], 1, V);
    for (int i = 1; i <= n; ++i) xym.modify(xym.root[i], xym.root[i - 1], 1, V, h[i], 1);
    for (int i = 1; i <= n - 1; ++i) yzh.modify(yzh.root[i], yzh.root[i - 1], 1, V, min(h[i], h[i + 1]), 1);
    
    for (int i = 1; i <= q; ++i) {
        int l, r, x;
        scanf("%d%d%d", &l, &r, &x);
        if (l == r)
            printf("%d\n", real[h[l]] > x);
        else if (x < real[1])
			puts("1");
		else {
        	int v = upper_bound(real + 1, real + V + 1, x) - real - 1;
        	printf("%d\n", xym.query(xym.root[l - 1], xym.root[r], 1, V, v + 1, V) - yzh.query(yzh.root[l - 1], yzh.root[r - 1], 1, V, v + 1, V));
		}
    }
    return 0;
}

标签:洛谷,trl,idx,int,题解,tree,mid,2024,trr
From: https://www.cnblogs.com/XuYueming/p/18016461

相关文章

  • HGAME2024-WEB WP
    WEEK12048*16直接把前端全部扒下来,自己搭建一个本地的环境,我这里用vscode搭建了一个。然后看下js代码,这里混淆了一堆,实在是难看,就找关键的地方,题目所说的32768找到了上面这个算式,他的结果就算32768,所以我们只需要将这里修改:然后本地起服务,随便玩几下,即可得到flag:Bypas......
  • 洛谷【算法1-5】 贪心
    P2240【深基12.例1】部分背包问题-洛谷|计算机科学教育新生态(luogu.com.cn)#include<bits/stdc++.h>usingnamespacestd;typedeflonglongll;constintN=110;intn,t;structnode{intm,v;};boolcmp(nodeaa,nodebb){returnaa.v*bb.m>bb.v*aa.m......
  • P9089 「SvR-2」Work 题解
    P9089「SvR-2」Work可以找到一些性质:如果串\(c(字符)+A\)合法则串\(A\)合法,反之如果串\(A\)不合法则串\(c(字符)+A\)不合法如果串\(A,B\)合法(\(len(A)<len(B)\))且\(c+A\)合法,则\(c+B\)合法,而长度最小的合法串一定是一个后缀组成的那么可以得到以下算法用一......
  • 「题解」P6130 随机红包
    在\([0,1]\)上随机撒\((n-1)\)个点划分成\(n\)段,求第\(k\)大的段长的期望。从Appleblue17老师的题解中学的,大概详细写很多一笔带过但是我不认为很简单的步骤。Part1令随机变量\(X\)为第\(k\)大的段长。\(E(X)=\int_0^1P(X=x)x\textdx=\int_0^1P(X\geqx)\text......
  • 「题解」ARC139F Many Xor Optimization Problems
    考虑线性空间的标准基底(即每个主元都只有对应向量有值),答案为所有基底异或和。对于一个秩\(k\)计算它对答案的贡献。固定主元为\(a_1<a_2<\cdots<a_k\),各种情况应该是等概率,也就是对第\(i\)个基底来说,\(a_i\)位一定为\(1\),再往下的位除了在\(a\)出现过的以外的位0/1是......
  • CF1928E题解
    ModularSequence题目传送门题解发现\(a_i+y\)与\(a_i\bmody\)均不改变\(a_i\)模\(y\)的余数,所以答案序列的每个元素均可表示为\(x\bmody+ky\)的形式,先让\(s\)减去\(n\times(x\bmody)\),再除以\(y\),这样原序列可以被划分为一个从\(\lfloor\dfrac{x}{y}\rflo......
  • luogu6600题解
    题意中的字母T可以看作一个回文的\(1\)串和回文串中心带一个向下的\(1\)串。我们先来考虑朴素做法,可以枚举这个中心然后扩展来找有几个符合要求的串。朴素做法显然复杂度不对。沿着朴素的思路优化。如果只考虑\(w\gea\)和\(h\geb\)这两个要求很容易想到容斥。此......
  • 20240214打卡
    在Android中,可以通过定义drawable文件来创建自定义的图形、形状、背景等,然后在布局文件中应用这些drawable文件作为背景或者图标。同时,也可以通过定义样式(style)来设定布局以及控件的样式,从而实现一致的外观和风格。下面展示如何定义drawable文件以及样式,并将其应用到布局和控件中......
  • 20240215打卡
    使用MPAndroidChart第三方框架绘制柱状图:1.**在build.gradle文件中添加依赖项**(低版本可以导入jar包):打开您的项目的build.gradle文件,然后在dependencies部分添加MPAndroidChart的依赖项。```groovydependencies{implementation'com.github.PhilJay:MPAndroidCh......
  • 20240206打卡
    自定义软键盘通常涉及两个方面:设计自定义键盘布局和管理键盘的显示和隐藏。自定义绘制和使用软键盘:1.**设计自定义键盘布局**:创建一个自定义的XML布局文件,定义您想要的键盘布局。您可以使用`Button`或其他视图来表示键。例如,创建一个名为`custom_keyboard.xml`的布局文件。......