【线段树】入门-HDU - 1754 例题讲解,超级详细 +codeforces 1023 D (实际应用)

版权声明:版权所有……啊重点是我简直找不到第二个人写比我还多的吐槽-。- https://blog.csdn.net/StrongerIrene/article/details/81878860

以下分析基于这道题目。。和代码。。

讲解在后面别被题目吓到哈哈哈哈哈

#include<iostream>
#include<cstdio>
using namespace std;
int n,n_,m,a[200001<<2];char s;
void init(int w){
	n=1;
	while(n<w)n*=2;
	for (int i=1;i<=2*n-1;i++) a[i]=0;
}

void update(int k,int b){
	k+=n-1;
	a[k]=b;
	while(k>0) k/=2,a[k]=max(a[k*2],a[k*2+1]);
}

int query(int e,int b,int k,int l,int r){
	if (r<e||b<l) return 0;
	if (e<=l&&r<=b) return a[k];
	else {
		int vl=query(e,b,k*2,l,(l+r)/2);
		int vr=query(e,b,k*2+1,(l+r)/2+1,r);
		return max(vl,vr);
	}
}

int main(){
	while(~scanf("%d %d",&n_,&m)){
		init(n_);int t1,t2;
		for (int i=1;i<=n_;i++){
			scanf("%d",&a[i+n-1]);
		}
		for (int i=n-1;i>0;i--) a[i]=max(a[i*2],a[i*2+1]);
		for (int i=1;i<=m;i++){
			scanf("%*c%c %d %d",&s,&t1,&t2);
			if (s=='U') update(t1,t2);
			if (s=='Q') printf("%d\n",query(t1,t2,1,1,n));
		}
	}
	return 0;
}

(1)如果一共有9个结点, 先初始化,n=1, 直到n=16(9-16是空的,没话说)
while循环退出的时候是16啊,还是远远大于w了
然后需要二倍的放在上面,先初始化成0
(2)读入数据:
1就是1+16-1(16)
2就是2+16-2(17)
作为一棵树,它如果是2,上面结点的末尾数字是2的n次方-1 ,没错
随后更新,当然要自底向上,并且从n-1到1更新,
每次都是i=i*2和i*2+1里面的max
【注意】孜杰点(划掉)子结点分别是2*i和2*i-1

(3)随后,分别进行update和query
update:
小弟送礼一层一层递给大哥
啊,更新的时候,结点是k,值是b,反映过去当然k要+(n-1)
这样才能先到树上的结点。(先把树上的子结点,最小的那个叶子结点更新成为b)
随后,因为它是2*i或者2*i+1来的,/2就可以拿到那个根节点了,根节点于是也更新一下。
(4)l和r是你所在的提供服务的区间,e和b是想要查询的区间。
如果r<e,或者b<l,不好意思超出了服务范围,你的请求被拒绝了
如果这个区间正好囊括了【注意,是两边儿都相等】,就返回结点编号
结点编号,就是我们在上面初始化搞搞搞搞过的n(扩大了)
比如1-n区间,最小的就是结点编号为1的那个。
比如1-16,我们想查询3-7
【以下为演示】------

开始l=1 ,r=n,
先向左找e和b,(啊,其实就是左边的结点啊 )1的话就是*2 然后就是2, +1就是3,其实就是往下递归的意思呀。。。。
递归的时候这个结点能覆盖到的区间是什么呢...
l是肯定的,因为是左边的,
右边呢,下去和上来是一个原理,(1+16)/2=8 
结点编号为2,维护的是1-8 
其次,结点编号为3,8用过了,这里就是维护的9-16(虽然这个题里面9-16都是空的,但是数组也要这么长---也罢这个可忽略不看)
然后一直递归就好了呀,到了(3,4)的时候,e<=l  r<=b 就返回了覆盖3 4 代表的结点的值a[k]
因为是最大值或者最小值,返回值里控制了一个max。


还是我家老抽写的代码简洁清楚,我看的蓝书白书一头雾水。。。。哈哈哈哈~ 待我把D维护一下去了

-------------------------------------------------------------------------------------------------

好了,D写完了,发现自己以前的理解有很多偏差

---上面写错了----

上面代码里面的n,就是最后一行的数量,nnn就是你实际有的结点的数量

for (int i = 1; i <= nnn; i++) {
		cin >> tmp;
		if (tmp == 0)tmp = inf;
		a[i + n - 1] = tmp;
	}

a[i+n-1] 可以用(nnn个,然后对应的是在线段树里面的下标)

查询的时候,如果想查3,就是(3,3,1,1,n)

后面三个数字的意思是,覆盖了1-n(底层)结点的在线段树里面结点编号是1

再比如,1 2 3 4  ,上面有4个,(nnn=4,n=4,但是一个4是最下面那一排有几个,一个是数据有多少)

所以1在线段树里面的结点编号已经是4了

读入的时候也是,因为上面有n-1个结点,所以维护的时候都是i+(n-1)

                           1(1-n)(1-4)

           2(1-2 )              3(3-4)

4(1-1)      5(2-2)         6(3-3)            7(4-4)

D题: 链接

用线段树维护最小值。

【注意】,上面的代码是最大值,因此是max,最小值就要改成min,初始化的时候也要改成inf

【q查询的时候找不到也要return inf】

【如何解决第一行就爆出“请按任意键继续”?】
这是因为stackoverflow……
开在里面就是容易爆,开全局数组。。。。

这个题目有个小坑,首先要处理0的问题,其次

【隐含条件】【隐含条件】【隐含条件】

如果是4 4 那么4必须要出现,因为每次至少维护一个区间。这时候。。。下标就很容易乱掉了,我改了那么久。。。瞎鸡儿写真的是

另外,作为线段树要开大4倍空间,因为首先,200000的话需药2^k至少是262144,然后要扩大一倍的话,。。。*&()所以我数组都放大了4倍过去的。。

4*maxn哦

后面的我已经头晕眼花了,…… 看都没看随便扔上去……

onj:“我看你是什么都不懂哦!!!!!”


#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn int(2e5+5)
#define inf 0x3f3f3f
int n, nnn, m, k, a[4*maxn];
char s;
int d1[4 * maxn];
int ddd[4 * maxn];
int d2[4*maxn];
int rec[4* maxn];
void init(int w) {
	n = 1;
	while (n<w)n *= 2;
	for (int i = 1; i <= 2 * n - 1; i++) a[i] = inf;
}

void update(int k, int b) {
	k += n - 1;
	a[k] = b;
	while (k>0) k /= 2, a[k] = min(a[k * 2], a[k * 2 + 1]);
}

int query(int e, int b, int k, int l, int r) {
	if (r<e || b<l) return inf;
	if (e <= l && r <= b) return a[k];
	else {
		int vl = query(e, b, k * 2, l, (l + r) / 2);
		int vr = query(e, b, k * 2 + 1, (l + r) / 2 + 1, r);
		return min(vl, vr);
	}
}

int main() {
	cin >> nnn >> k;
	init(nnn);
	//int inf = 200001;
	int tmp;
	for (int i = 1; i <= nnn; i++) {
		cin >> tmp;
		if (tmp == 0)tmp = inf;
		a[i + n - 1] = tmp;
	}
	for (int i = n - 1; i>0; i--) a[i] = min(a[i * 2], a[i * 2 + 1]);
	//这大概是初始化时两种必备工作
	int cnt = 0;
	int cnt0 = 0; int rec0 = 0;
	for (int i = 1; i <=nnn; i++) {
		//for every a[i]
		int dd = a[i + n - 1];
		if (a[i + n - 1] == inf) {
			cnt0++;
			rec0 = i + n - 1;
			continue;

		}
		if (d1[dd] == 0) {
			d1[dd] = i;//first appear
			cnt++;
			rec[cnt] = dd;//rec remembers how much numbers are there
			d2[dd] = i;
		}
		else //d1[dd]!=0
			d2[dd] = i;//last appear, others don't care
	}
	/////-------------------
	////finds
	/////-------------------
	bool f = 1;
	for (int i = 1; i <= cnt; i++) {
		if (query(d1[rec[i] ] , d2[rec[i]] , 1, 1, n)<rec[i]) {
			f = 0; break;
		}
	}
	/////-------------------
	////else 
	/////-------------------

	if (d1[k] == 0&&cnt0==0)f = 0;
	else if(d1[k]==0&&cnt0!=0){
		a[rec0] = k;
	}


	//注意  我们要特判一下 如果最大的数字没出现过 并且也没有0  是不行的
	if (f) {
		cout << "YES" << endl;
		if (a[1] == inf) {
			for (int i = 1; i <= nnn; i++)if (i == nnn)cout << k << endl; else cout << k << " ";
		}
		else {
			
			for (int i = 1; i <= nnn; i++) {
				if (a[i + n - 1] != inf)
					ddd[i] = a[i + n - 1];
				else //如果是inf的话!
				{
					
					if (i == 1) {
						int tmp = i + n - 1;
						while (1) {
							if (a[tmp] == inf)tmp++;
							else break;
						}
						ddd[1] = a[tmp];
					}
					else { ddd[i] = ddd[i-1]; }
				}
			}
		
		/*
		for (int i = 1; i <= nnn; i++) {
			if (a[i + n - 1] != inf)
				ddd[i] = a[i + n - 1];//cout<<a[i+n-1];
			else {
				if (a[i + n - 1] == inf)a[i + n - 1] = 0;
				if (i == n)ddd[i] = a[i + n - 2];
				else  ddd[i] = a[i + n];
			}*/
			for (int i = 1; i <= nnn; i++) {
				//if ()ddd[i] = 0;
				if (i == nnn)cout << ddd[i] << endl;
				else cout << ddd[i] << " ";
			}
		}
	}
	
	else cout << "NO" << endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/StrongerIrene/article/details/81878860