线段树
实在不好意思不写了哈哈哈啊哈。
线段树主要是解决一些大量操作区间并且维护区间的题目(维护的区间要有加和性)。
线段树核心函数有以下几个
- pushup()(主要用来向上跟新节点信息)
- pushdown()(用来向下更新来不及更新的节点)
- query()(查询当前区间的维护值)
- updata()(更新一个区域或者一个点的信息)
注意
单点更新是不需要pushdown函数的,并且维护信息里面没有lazy标记(具体会在区间更新里面详细叙述),而且线段树尽量用scanf和printf更快。
不多说放题!!!
hdu 1166 戳这里
这是一道典型的区间单点更新问题,只需要找到根节点并且修改值,并且用pushup()传到树顶就行。但是值得注意的是线段树的题目一般操作次数很多,数据有的时候很大,用long long int存比较好,而且线段树的数组需要开到区域的4倍大小,不然很容易runtime!
#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
#include<iomanip>
#define MAXN 50000
using namespace std;
#define ll long long int
struct node{
ll key;
ll lazy;
};
node p[4*MAXN];
ll num[4*MAXN];
ll q=1,n;
void pushup(node p[],ll n)
{
p[n].key=p[2*n].key+p[2*n+1].key;
}
void buildtree(node p[],ll n,ll l,ll r)
{ p[n].lazy=0;
if(l==r)
{ p[n].key=num[q];
q++;
return ;
}
ll mid=(l+r)/2;
buildtree(p,2*n,l,mid);
buildtree(p,2*n+1,mid+1,r);
pushup(p,n);
}
void pushdown(node p[],ll n,ll l,ll r)
{
if(l!=r)
{ ll mid=(r+l)/2;
p[2*n].lazy=p[n].lazy;
p[2*n].key=(mid-l+1)*p[2*n].lazy;
p[2*n+1].lazy=p[n].lazy;
p[2*n+1].key=(r-mid)*p[2*n+1].lazy;
}
p[n].lazy=0;
}
void updata(node p[],ll n,ll l,ll r,ll i,ll c)
{ ll mid=(l+r)/2;
if(l==r&&l==i)
{
p[n].key+=c;
return ;
}
if(i<=mid)
updata(p,2*n,l,mid,i,c);
else
updata(p,2*n+1,mid+1,r,i,c);
if(l!=r)
pushup(p,n);
}
ll query(node p[],ll n,ll l,ll r,ll x,ll y)
{
if(l==x&&r==y)
return p[n].key;
if(p[n].lazy)
pushdown(p,n,l,r);
ll mid=(l+r)/2;
if(y<=mid)
return query(p,2*n,l,mid,x,y);
else if(x>mid)
return query(p,2*n+1,mid+1,r,x,y);
else
return query(p,2*n,l,mid,x,mid)+query(p,2*n+1,mid+1,r,mid+1,y);
}
int main()
{
ll t;
ll w;
scanf("%lld",&t);
w=t;
while(t--)
{ q=1;
ll n;
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
{
ll a;scanf("%lld",&a);
num[i]=a;
}
buildtree(p,1,1,n);
printf("Case %lld:\n",w-t);
string s;
while(cin>>s)
{
if(s=="End")
break;
if(s=="Query")
{
ll i,j;
scanf("%lld%lld",&i,&j);
printf("%lld\n",query(p,1,1,n,i,j));
}
if(s=="Sub")
{
ll i,j;
scanf("%lld%lld",&i,&j);
updata(p,1,1,n,i,-1*j);
}
if(s=="Add")
{
ll i,j;
scanf("%lld%lld",&i,&j);
updata(p,1,1,n,i,j);
}
}
}
}
区间更新
区间跟新的题目比比单点更新稍微多一点东西,比如pushdown函数和lazy标记。
首先 lazy标记有什么用处?
由于线段树的大量操作,所以全部更新到树根未免太浪费时间了,所以我们用lazy标记保存住这个更新结果,如果我们下次访问需要更新到这个区间以下,再去用pushdown()更新。
hdu 1698 戳这里
#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
#include<iomanip>
using namespace std;
#define MAXN 200000
#define ll long long int
struct node{
ll key;
ll lazy;
};
node p[4*MAXN];
ll q,n;
void pushup(node p[],ll n)
{
p[n].key=p[2*n].key+p[2*n+1].key;
}
void buildtree(node p[],ll n,ll l,ll r)
{ p[n].lazy=0;
if(l==r)
{
p[n].key=1;
return ;
}
else
{ ll mid=(l+r)/2;
buildtree(p,2*n,l,mid);
buildtree(p,2*n+1,mid+1,r);
}
pushup(p,n);
return ;
}
void pushdown(node p[],ll n,ll l,ll r)
{
if(p[n].lazy)
{ ll mid=(l+r)/2;
p[2*n].lazy=p[n].lazy;
p[2*n].key=p[2*n].lazy*(mid-l+1);
p[2*n+1].lazy=p[n].lazy;
p[2*n+1].key=(r-mid)*p[2*n+1].lazy;
}
p[n].lazy=0;
}
void updata(node p[],ll n,ll l,ll r,ll x,ll y,ll lazy)
{
if(l==x&&r==y)
{ p[n].lazy=lazy;
p[n].key=(r-l+1)*p[n].lazy;
return ;
}
if(p[n].lazy)
pushdown(p,n,l,r);
ll mid=(l+r)/2;
if(x<=mid)
updata(p,2*n,l,mid,x,min(y,mid),lazy);
if(y>mid)
updata(p,2*n+1,mid+1,r,max(x,mid+1),y,lazy);
if(l!=r)
pushup(p,n);
}
int query(node p[],ll n,ll l,ll r,ll x,ll y)
{
if(l==x&&r==y)
{
return p[n].key;
}
ll mid=(l+r)/2;
if(y<=mid) return query(p,2*n,l,mid,x,y);
else if(x>mid) return query(p,2*n+1,mid+1,r,x,y);
else return query(p,2*n,l,mid,x,mid)+query(p,2*n+1,mid+1,r,mid+1,y);
}
int main()
{ int t;
scanf("%d",&t);
int w=t;
while(t--)
{
scanf("%lld%lld",&n,&q);
buildtree(p,1,1,n);
for(ll i=1;i<=q;i++)
{
ll x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
updata(p,1,1,n,x,y,z);
}
printf("Case %d: The total value of the hook is %lld.\n",w-t,query(p,1,1,n,1,n));
}
}