以下分析基于这道题目。。和代码。。
讲解在后面别被题目吓到哈哈哈哈哈
#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;
}
------------1754自己又写了一遍,找不同选手竟然狂wa不止。。。。。
还是有问啊。。scnaf %s要char s[20] 才行 还有就是理解成 (数据输入之后 ) k*2,k*2+1
而且不是k这个,是a[..] 这个是下标
还有更严重 query里面那个越界是实在没法看的
e b l r
是e比r都大 b比l都小
还有就是吃了你的包含---想问1-7的 遇到3-4
如果是想问3-4的遇到1-7就返回 还差个屁
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
#define maxn 200005
//const int maxn 20005;
int a[(maxn<<2)+2];
int tree_n;int num_n;//稍微区分一下
//然后我的工作只要初始化好了就好啦!
void init(int num_n){
tree_n=1;
while(tree_n<num_n)tree_n=tree_n*2;
for(int i=1;i<=tree_n*2;i++)a[i]=0;
//一定要初始化成不影响的值? 依据想要最小值还是最大值/xxxx。。而定
}
void update (int k ,int v){//k是输入的:从哪里开始 v是那个值
k=k+tree_n-1;//这样方可到达树下
a[k]=v;
while(k>0){
k=k/2;
a[k]=max(a[k*2],a[k*2+1]);
} //自底向上更新,也还是没错
}
int query(int e,int b ,int x,int l,int r){//递归调用,只要分三种情况就可以
//使用的时候l和r是很大的1到n,慢慢开始变小。。。
//e和b是小的 包含3种情况:
// e l r b
if(r<e||b<l)return 0;//return 什么
// l e b r
else if(e<=l&&r<=b) return a[x];
//*** 这个k代表的是标号啊 在标号什么什么的树里面找最大的
// 比如进来的时候是1 就是全1里面最大的 而1囊括的范围又是1-n
else {
int ll=query(e,b,x*2,l,(l+r)/2);
int rr=query(e,b,x*2+1,(l+r)/2+1,r);
return max(ll,rr);
}
//(1) 你想查询的区间e b是一直不变的,只不过是return的max在更新,我们也只需要这个max就好了
//情况一发生在 查找的范围小,区间大(要2-7,那么3-4肯定符合)才是
//剩下的就是把l扩大了,扩大的时候每个人要管他对应的区间
//每个区间至少有2个呀。似乎一直是奇数偶数 奇数偶数。。 所以才不断往下更新去找呀233
}
int main(){
int m;
while(~scanf("%d %d",&num_n,&m)){//cin>>num_n>>m){
init(num_n);
for(int i=1;i<=num_n;i++){
scanf("%d",&a[i+tree_n-1]);
//cin>>a[i+tree_n-1];
}
//哇 这里千万别忘记后续
for(int i=tree_n-1;i>=1;i--)a[i]=max(a[2*i],a[2*i+1]);
//string s;
char s[20];
int t1,t2;
for(int i=1;i<=m;i++){
// cin>>s>>t1>>t2;
scanf("%s",s);
scanf("%d %d",&t1,&t2);
if(s[0]=='U') update(t1,t2);
else if(s[0]=='Q') //cout<<query(t1,t2,1,1,tree_n)<<endl;
printf("%d\n",query(t1,t2,1,1,tree_n));
}
}
return 0;
}
哇 线段树
-----改变心理大作战
(1)去除无关干扰?
其实完全可以一边写一边bb的(
(2)先初始化,总没错吧?
分解分解分解,分解到你愿意做这件事
之后的事情,仅仅是把分解都穿起来,并且加快的单个速度
就完全可以完成的(?)
(3)
没跑了,17个的话,treen是树上的,有16*2 就是32个,树上的值对应到num_n-1是实际的
初始化的时候全部都会拿到,除了用不到的。不怕
(4)
query的时候,为啥一边越界就可以返回0了?
除了越界、包括之外,剩下的应该就是区间不完全匹配,
注意,这里只是查询,a[k]都写好了,必要时候还要靠最小区间的a[k]活命,因此完全不用更改a[k]