C++算法 线段树

线段树这个算法,看起来很是高端,并且颇有用处,因此仍是讲一下下吧。算法

舒适提示:写线段树前请作好写码5分钟,调试一生的准备^-^数组

啊直接步入正题……优化

首先咱们考虑一个题目:有一个序列,要作到单点修改单点查询,该怎么作呢?调试

同窗们先不要着急关掉……咱们细细分析,像这种题,明显你们都知道……直接暴力就过了嘛……,因此不作分析……blog

而后咱们考虑第二个题目:有一个序列,要作到单点修改区间求和,该怎么作呢?递归

像传统的for(int i=1;i<=n;i++)ans+=a[i]固然是很香的,但若是遇到一些很是奇怪的题目(数 据 大 到 你 自 闭),这个就没什么用了……get

 

啊这,简单的暴力不能用,就要用复杂的暴力——线段树,固然是简单亿点点的~由于单点修改能够O(1),不存在浪费时间的事情,因此咱们用线段树小小的处理一下就行了。zsh

接下来画一个图帮助斯烤:模板

 

 

在这个图上呢……一共有8个点,就是底下那些最小的线段,而咱们把他合成了许多部分,每一个部分的值就是f[wz*2]+f[wz*2+1](f是每一个的值,wz是每一个的编号)。class

而后就能够把他一直向下,一直向下,直到发现这个区间是被咱们要求的区间所包含的,而后这个区间内的数就所有加上,就这么一直向下找找找……,最后就能够算出一个区间的总和。

下面是演示代码~

 

void qh(long long wz)//wz是如今所处的小块编号 
{
	if(tree[wz].l>=a&&tree[wz].r<=b)//a和b是咱们要求的区间的左右端点 
	{
		zshu+=tree[wz].shu;//所有被包含,直接加上 
		return;
	} 
	long long mid=(tree[wz].l+tree[wz].r)/2;//不能被包含?分裂一下试试吧
	if(b>mid)//和谐小细节~ 
	{
		qh(wz*2+1);//若是有被包含,就去右边的小块看看 
	}
	if(a<=mid)//和谐小细节*2~(至于为啥有和谐小细节,以及为何要这样写,请同窗们本身斯烤(这么简单仍是能够的8)) 
	{
		qh(wz*2);//若是有被包含,也要去左边的小块看看
	}
}

 

嘿嘿简单吧^-^,啊我忘记讲建树了QAQ。没事立刻就讲。

建树嘛,直接递归就行了,每次把这个区间分红两份,一直分到l和r同样(就是说这是一个点的值,要输入了),而后获取两个点的值以后就能够向上递归~嗯嗯对,一直递归就建好了,相信你们都有这个写递归的能力,但要实在不会就看看下面的代码吧(舒适提示:线段树用来建树的数组要开到节点数的4倍大,至于为何我也忘了……):

struct hehe
{
	long long l,r,shu,f;
}tree[400005];//tree结构体~ 
long long mid;
void js(long long ll,long long rr,long long wz)//js,建树的意思。ll和rr分别是左右边界,wz就是他的编号 
{
	tree[wz].l=ll;//左边界是ll 
	tree[wz].r=rr;//右边界是rr 
	if(ll==rr)//左右边界一致,这是一个点 
	{
		scanf("%lld",&tree[wz].shu);
		return;
	}
	long long mid=(ll+rr)/2;//若是这不是一个点,确定能分红两部分 
	js(ll,mid,wz*2);//左边 
	js(mid+1,rr,wz*2+1);//右边 
	tree[wz].shu=tree[wz*2].shu+tree[wz*2+1].shu;//加起来 
}

看,是否是很是简单,接下来为了学的更深一点点,要把题目加难了(是的还要加难,但我相信大家必定能够学会的)

有一个序列,要作到区间增长区间求和,该怎么作呢?

啊这,这个是线段树里最难的一部分(起码我以为最难),直接下放显然不太现实……,会浪费掉不少没必要要的时间。正所谓科技发展在于懒人~,其实咱们也能够在不影响结果的状况下偷个懒是吧QWQ。

就像加同样~我知道这两个序列的和,我就不必去求全部单个序列是多少,加法和这个差很少,咱们能够看看有那个序列是全都要加的,而后直接算出它加完以后的值。这样求这个值的时候确定是不影响计算的(偷懒成功!)。但这时就会有一些同窗吐槽:"啊你这个不严谨啊,若是下一次求和是求这个序列的一部分怎么办呢?"其实呢,我刚才也写了是吧……

其实咱们也能够在不影响结果的状况下偷个懒是吧

我刚才说的是不影响,但这个操做明显影响了,在哪里影响了呢?就是只加了一个总序列,没有让他的一部分加上(偷懒失败QWQ)。但好像根本没有必要调整他的一部分啊,由于目前根本用不到,没说让作的事情咱们还要去作不是浪费时间吗?但又不能不加,怎么办呢?咱们能够设定一个懒标记,表示它以前被加了多少,这样呢,每次要求和的时候只要下放这个懒标记,而且加上该加的数,就能达到不说不作,最大程度优化时间,还保证正确hhhhh(偷懒成功)

至于代码的实现也是很是的简单:

void xf(long long wz)
{
	tree[wz*2].shu+=(tree[wz*2].r-tree[wz*2].l+1)*tree[wz].f;//每一个长度单位都加上tree[wz].f,总值就增长了(tree[wz*2].r-tree[wz*2].l+1)*tree[wz].f
	tree[wz*2].f+=tree[wz].f;//以前每个长度单位要加tree[wz*2].f,如今发现它还要加上tree[wz].f,就把他们两个加起来就行了嘛,很容易的。 
	tree[wz*2+1].shu+=(tree[wz*2+1].r-tree[wz*2+1].l+1)*tree[wz].f;
	tree[wz*2+1].f+=tree[wz].f;//同理
	tree[wz].f=0;//这里已经加过了,要清零的,就像转帐同样,你给另外一我的转了钱,你钱就没了。 
}

上方是下放懒标记的代码,下方是区间修改代码:

void xg(long long wz)
{
	if(tree[wz].l>=a&&tree[wz].r<=b)
	{
		tree[wz].shu+=(tree[wz].r-tree[wz].l+1)*c;//更改值 
		tree[wz].f+=c;//增长懒标记 
		return;
	}//啊后面的都说过,不打了… 
	tree[wz].shu+=(min(tree[wz].r,b)-max(tree[wz].l,a)+1)*c;
	long long mid=(tree[wz].l+tree[wz].r)/2;
	if(b>mid)
	{
		xg(wz*2+1);
	}
	if(a<=mid)
	{
		xg(wz*2);
	}
}

至于加了懒标记之后别的代码也是要动的。

好比在原来的求和代码里多了一个xf(wz);

至于加在哪里同窗们本身斯烤吧(我太仁慈了)

啊我再放一道线段树模板题,你们能够秒掉来吊打我:模板题传送们

好了我以为我讲完了,若是有什么很差或漏掉的地方你们能够及时评论,我会更改的。

相关文章
相关标签/搜索