看了好久的zkw线段树,终于懂了一点点,在杭电刷了两道入门题,分别是”单点更新,区间求和“以及“单点更新,区间最值”
- 单点更新,区间求和:
HDU1166敌兵布阵
http://acm.hdu.edu.cn/showproblem.php?pid=1166
Code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <set>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn = 50002;
int T,n,m;
int t[maxn*4];
char s[10];
int query(int a,int b)
{
int ans = 0;
//a^b判断是否为兄弟节点(是则等于1),1^1 = 0;
for (a = a+m-1,b = b+m+1;a^b^1;a >>= 1,b >>= 1)
{
/*~比&优先,先对s取反,使得最低位1变0,0变1,再&1,
原来是最低位是0的就得到1&1=1,a对应左儿子,a^1就是相应的右儿子了*/
if (~a&1) ans += t[a^1]; /*如果是左子树的左孩子,则处理左子树右孩子*/
if (b&1) ans += t[b^1]; /*若是右子树的右儿子,处理右子树的左孩子*/
}
return ans;
}
void add(int a,int b)
{
//更新时循环的写法,如果像build_tree()那样就没意义了(因为根本没提速。。。。)
for (t[a+=m] += b,a >>= 1;a;a >>= 1)
{
t[a] = t[a*2] + t[a*2+1];
}
}
void sub(int a,int b)
{
for (t[a+=m] -= b,a >>= 1;a;a >>= 1)
{
t[a] = t[a*2] + t[a*2+1];
}
}
void build_tree()
{
for (m = 1;m <= n+1;m *= 2);
for (int i = m+1;i <= m+n;i ++)
scanf("%d",&t[i]);
for (int i = m-1;i > 0;i --)
{
t[i] = t[2*i] + t[2*i+1];
}
}
int main()
{
int k = 0;
scanf("%d",&T);
while (T --)
{
printf("Case %d:\n",++k);
memset(t,0,sizeof(t));
scanf("%d",&n);
build_tree();
int a,b;
while (1)
{
scanf("%s",s);
if (strcmp(s,"Add") == 0)
{
scanf("%d%d",&a,&b);
add(a,b);
}
if (strcmp(s,"Sub") == 0)
{
scanf("%d%d",&a,&b);
sub(a,b);
}
if (strcmp(s,"Query") == 0)
{
scanf("%d%d",&a,&b);
cout << query(a,b) << endl;
}
if (strcmp(s,"End") == 0)
break;
}
}
return 0;
}
- 单点更新,区间最值
HDU1754 I Hate It
http://acm.hdu.edu.cn/showproblem.php?pid=1754
Code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <set>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn = 200000+2;
int n,m,a,b,M;
char s[2];
int T[4*maxn];
void build_tree()
{
for (M = 1;M <= n+1;M *= 2);
for (int i = M+1;i <= M+n;i ++)
scanf("%d",&T[i]);
for (int i = M-1;i > 0;i --)
T[i] = max(T[i*2],T[i*2+1]);
}
//询问s~t同学之间
int query(int s,int t)
{
int ans = -1;
for (s = s+M-1,t = t+M+1;s^t^1;s >>= 1,t >>= 1)
{
if (~s&1) ans = max(ans , T[s^1]);
if (t&1) ans = max(ans , T[t^1]);
}
return ans;
}
//s号同学成绩改为t
void update(int s,int t)
{
for (T[s+=M] = t,s >>= 1;s;s >>= 1)
{
T[s] = max(T[s*2] , T[s*2+1]);
}
}
int main()
{
while (cin >> n >> m)
{
memset(T,0,sizeof(T));
build_tree();
while (m --)
{
scanf("%s",s);
if (strcmp(s,"Q") == 0)
{
scanf("%d%d",&a,&b);
cout << query(a,b) << endl;
}
if (strcmp(s,"U") == 0)
{
scanf("%d%d",&a,&b);
update(a,b);
}
}
}
return 0;
}
一些感想:
ZKW线段树博大精深,还有很多信息有待挖掘,一些值得注意的地方在这里列下:细节1
int t[maxn*4];
考虑到 n< m<2*n,开两倍m也就是要四倍n的大小细节2–build_tree()函数
void build_tree()
{
for (m = 1;m <= n+1;m *= 2);
for (int i = m+1;i <= m+n;i ++)
scanf("%d",&t[i]);
for (int i = m-1;i > 0;i --)
{
t[i] = t[2*i] + t[2*i+1];
}
}
1.for (m = 1;m <= n+1;m *= 2);
求m,这里保险起见,还是加上1好
2.输入从m+1开始的,但是建树是从m-1开始的,这样子留个空的t[m]是为了便于进行区间操作。 于是所建的树就会使这个样子:
也就是第一个输入的数据让他的兄弟是空的(t[m] = 0)
- 细节3-query(int a,int b)函数
int query(int a,int b)
{
int ans = 0;
for (a = a+m-1,b = b+m+1;a^b^1;a >>= 1,b >>= 1)
{
if (~a&1) ans += t[a^1];
if (b&1) ans += t[b^1];
}
return ans;
}
这个函数用了很多技巧,如for (a = a+m-1,b = b+m+1;a^b^1;a >>= 1,b >>= 1) , 有如if (~a&1) ans += t[a^1];
这两点需要多次反复看。
- 细节4
memset(t,0,sizeof(t));
这个必须而且一定放在build_tree()前。