线段树算法

二叉链式结构

struct node
{
    int ld,rd;
    node *lc,*rc;
    keytype key;
}

顺序存储结构

struct Node//节点i的左右孩子分别在2*i和2*i+1中
{
    int ld,rd;
    keytype key;
}Tree[Maxsize];

线段树操作的实现(顺序存储)

建立空线段树

void buildtree(int i,int a,int b)
{
    Tree[i].ld = a;
    Tree[i].rd = b;
    if(b-a == 1) return;
    buildtree(i*2,a,(a+b)/2);
    buildtree(i*2+1,(a+b)/2,b);
    return;
}

插入区间

void insert(int i,int a,int b,keytype key)
{
    if(Tree[i].key == 0){//未被完全覆盖
        int m = (Tree[i].ld+Tree[i].rd)/2;
        if(a==Tree[i].ld && b==Tree[i].rd){
            //根据key处理Tree[i].key
            return;
        }
        else if(b<=m) insert(i*2,a,b,key);//左边
        else if(a>=m) insert(i*2+1,a,b,key);//右边
        else{//一分为二
            insert(i*2,a,m,key);
            insert(i*2+1,m,b,key);
        }
    }
}

查找区间

keytype search(int i,int a,int b)
{
    keytype res;
    if(a<=Tree[i].ld && Tree[i].rd <=b){//完全覆盖
        //根据Tree[i],key处理res
        return res;
    }
    if(a<(Tree[i].ld+Tree[i].rd)/2) search(2*i,a,b);
    if(b>(Tree[i].ld+Tree[i].rd)/2) search(2*i+1,a,b);
    return res;
}

删除区间

void delete(int i,int a,int b)
{
    int m = (Tree[i].ld+Tree[i].rd)/2;
    if(a<=Tree[i].ld && Tree[i],rd<=b)
    {
        //对Tree[i].key做相应的处理
        return;
    }
    else if(a<m) delete(i*2,a,b);
    else if(b>m) delete(i*2+1,a,b);
    else{
        delete(i*2,a,m);
        delete(i*2+1,m,b);
    }
}

计算区间长度

int QLen(i)
{
    if(Tree[i].key = 1) return Tree[i].rd-Tree[i].ld;//完全覆盖
    else if(Tree[i].rd-Tree[i].ld == 1) return 0;//叶子
    else return QLen(i*2)+Q(i*2+1);
}

相关例题:

zoj1610 Count the Colors

#include <iostream>
#include<string.h>
using namespace std;
#define maxn 8000
#define NOCOLOR -1
#define DIVCOLOR -2

//线段树节点
struct node
{
    int left;
    int right;
    int key; //存储颜色
} Tree[maxn * 3];//注意节点要存多几倍的

//初始化线段树
void Init(int l, int r, int index)
{
    Tree[index].left = l;
    Tree[index].right = r;
    if (Tree[index].left + 1 == Tree[index].right)//7到叶子节点了
        return;
    int mid = (l + r) / 2;
    Init(l, mid, 2 * index);
    Init(mid, r, 2 * index + 1);
}

void Insert(int l, int r, int index, int key)
{
    //相同颜色则不做插入
    if (Tree[index].key == key)
        return;
    //可以完全覆盖
    if (Tree[index].left == l && Tree[index].right == r)
    {
        Tree[index].key = key;
        return;
    }
    //不能完全覆盖
    //如果不是已经分好的,要么就是这一个段全都是一个颜色,要么就是没颜色
    if (Tree[index].key != DIVCOLOR)//如果这一段是有多种颜色的话
    {//下推到子节点
        Tree[2 * index].key = Tree[index].key;
        Tree[2 * index + 1].key = Tree[index].key;
    }
    Tree[index].key = DIVCOLOR;//让这个节点标记有多种颜色
    int mid = (Tree[index].left + Tree[index].right) / 2;
    if (r <= mid)
    {
        Insert(l, r, 2 * index, key);
        return;
    }
    if (l >= mid)
    {
        Insert(l, r, 2 * index + 1, key);
        return;
    }
    Insert(l, mid, 2 * index, key);
    Insert(mid, r, 2 * index + 1, key);
}

int colorsave = -1;
int m[maxn + 5];

void countcolor(int index) //搜索Tree[index]有多少种颜色
{

    if (Tree[index].key == DIVCOLOR) //里面有多种颜色
    {//递归遍历子树
        countcolor(2 * index);
        countcolor(2 * index + 1);
    }
    if (Tree[index].key == NOCOLOR) //没有颜色
    {
        colorsave = NOCOLOR;
    }
    if (Tree[index].key >= 0) //有同一种颜色
    {
        if (Tree[index].key != colorsave) //如果和前面的颜色不同,则要记录
        {
            m[Tree[index].key]++;
            colorsave = Tree[index].key;
        }
        return; //和前面的颜色相同,则直接return
    }
}

int main()
{
    int n;
    int l, r, k;
    //建树
    Init(0, maxn, 1);
    while (cin >> n)
    {
        //初始树
        Tree[1].key = NOCOLOR;
        while (n--)
        {
            cin >> l >> r >> k;
            Insert(l, r, 1, k); //插入树
        }
        memset(m, 0, sizeof(m)); //初始化m列表
        //计算颜色数量
        countcolor(1);
        //输出结果
        for (int i = 0; i <= maxn; i++)
        {
            if (m[i])
            {
                cout << i << " " << m[i] << endl;
            }
        }
        cout << endl;
    }
    return 0;
}

zoj2451 Minimizing maximizer

题意:给一个长度为n的区间,m条线段序列(也就是m个sorter),找出这个序列的一个最短子序列(最少的sorter数),使得区间完全被覆盖(使整个区间有序)

#include<iostream>
#include<stdio.h>
#include<memory.h>
using namespace std;

struct node{
	int step;
	int left,right;
}tree[130000];

const int MAX=0x7ffffff;
int N,M;

//初始化线段树,让每个节点的step都为无穷大 
void Init(int i,int a,int b)
{
	tree[i].step = MAX;
	tree[i].left = a;
	tree[i].right = b;
	if(a==b) return;//到叶子节点了
	int mid = (a+b)/2;
	Init(2*i,a,mid);
	Init(2*i+1,mid+1,b);
}

//插入,从tree的第i个节点往end节点中插入s 
void Insert(int i,int end,int s)
{
	if(s<tree[i].step){//从根往叶子更新step 
		tree[i].step = s;
	}
	if(tree[i].left==tree[i].right) return;//到叶子节点了
	//二分查找end值位置,沿路都进行赋值 
	int mid = (tree[i].left+tree[i].right)/2;
	if(end<=mid){
		Insert(2*i,end,s);
	}else{
		Insert(2*i+1,end,s);
	}
}

//从第i个节点搜索a到b最少需要多少根水管 
int Count(int i,int a,int b)
{
	if(a==tree[i].left && b==tree[i].right){
		return tree[i].step;
	}
	int mid=(tree[i].left+tree[i].right)/2;
	
	if(b<=mid){
		return Count(2*i,a,b);
	}
	if(a>mid){
		return Count(2*i+1,a,b);
	}
	//左右两边看这个区间段内所需step最小的 
	int t1,t2;
	t1 = Count(2*i,a,mid);
	t2 = Count(2*i+1,mid+1,b);
	
	if(t1<t2){
		return t1;
	}else{
		return t2;
	}
}

void proc()
{
	int t,x,y;
	Init(1,1,N);//建树 
	Insert(1,1,0);//从第i个节点开始搜索值为1节点,赋值step为0 
	for(int i=0;i<M;i++){
		scanf("%d %d",&x,&y);
		if(x<y){
			t = Count(1,x,y-1);//计算前面x~y-1最小的step 
			Insert(1,y,t+1);//从第i个节点开始搜索值为y节点,赋值t+1 
		}
	}
	printf("%d\n",Count(1,N,N));
}

int main(){
	while(scanf("%d %d",&N,&M)!=EOF){
		proc();
	}
	return 0;
}

poj2777 Count color

题意:一条很长(L)的画板,有T种颜色,O个操作;每次操作将一个区间刷成一种颜色,或者查询一个区间内所含的颜色数。

#include<iostream>
#include<stdio.h>
using namespace std;
#define maxn 300010

struct node
{
    int lc,rc;
    int state;    //用二进制的每一位中的1的个数来标记颜色的个数
    int id;       //标记状态看是否被完全覆盖
}tree[maxn];

//建树 
void build(int i, int l, int r)
{
    int mid;
    tree[i].lc=l;
    tree[i].rc=r;
    if(l==r)
        return;
    mid=(l+r)>>1;
    build(2*i, l, mid);
    build(2*i+1, mid+1,r);
}

//从s到t涂上颜色value,从T节点开始查找
void insert(int s,int t,int T,int value)  
{
    int mid;
    //说明涂色范围正好完全覆盖T所包含的区域
    if(tree[T].lc==s&&tree[T].rc==t)
    {
        tree[T].id=1; //标记为完全覆盖
        tree[T].state=1<<(value-1); //不同的颜色用2进制不同位上的1表示
        return;
    }
    //id!=0说明T是完全覆盖的,且现给定区域不能完全涵盖该节点的范围
    if(tree[T].id)
    {
        tree[T].id=0;//修改T节点id为0 
        tree[2*T].id=tree[2*T+1].id=1;//修改孩子节点id为1(传递)
        tree[2*T].state=tree[2*T+1].state=tree[T].state;//T节点原只有一种颜色,且完全覆盖,则传递这个只有一个颜色的status 
    }
    mid=(tree[T].lc+tree[T].rc)/2;
    if(t<=mid)// 插入的范围全部在左子树
        insert(s,t,2*T,value);
    else if(s>mid)// 插入的范围全部在右子树
        insert(s,t,2*T+1,value);
    else// 分别插入 
    {
        insert(s,mid,2*T,value);
        insert(mid+1,t,2*T+1,value);
    }
    //更新一路下来节点T的信息,经过的点都加上存在新增颜色的信息 
    tree[T].state=(tree[2*T].state)|(tree[2*T+1].state);//等同于把颜色数相加(并集) 
    //剪枝,合并节点信息 
    if(tree[2*T].id&&tree[2*T+1].id&&tree[2*T].state==tree[2*T+1].state)
        tree[T].id=1;
}

//从T节点开始查询区间s到t颜色状态(01串) 
int qurry(int s,int t,int T)
{
    if(tree[T].lc==s&&tree[T].rc==t)//如果完全覆盖 
        return tree[T].state;
    if(tree[T].id)//如果没有完全覆盖,但是更大范围的颜色数也只有1,就不用再往下遍历了 
        return tree[T].state;
    int mid=(tree[T].lc+tree[T].rc)/2;
    if(t<=mid)
        return qurry(s,t,2*T);
    else if(s>mid)
        return qurry(s,t,2*T+1);
    else
        return qurry(s,mid,2*T)|qurry(mid+1,t,2*T+1);
}

int main()
{
    int i,j,k,color,t,num,len,m;
    char cmd[2];
    while(scanf("%d%d%d",&len,&color,&m)!=EOF)
    {
        build(1,1,len);//建线段树[1,len] 
        tree[1].state=1;//初始根节点状态为只涂了1这个颜色 
        tree[1].id=1;//且只有一种颜色 
        while(m--)//进行m次事件 
        {
            scanf("%s",cmd);
            if(cmd[0]=='C')//进行涂色 
            {
                scanf("%d%d%d",&i,&j,&k);
                if(i>j)//特判ij顺序颠倒情况 
                {
                    t=i; i=j; j=t;
                }
                insert(i,j,1,k);//从根节点1开始搜索,往[i,j]中涂k色 
            }
            else//进行询问 
            {
                scanf("%d%d",&i,&j);
                if(i>j)//特判ij顺序颠倒情况 
                {
                    t=i; i=j; j=t;
                }
                k=qurry(i,j,1);//从根节点1开始搜索[i,j]区间内涂色情况 
                num=0;//数数 
                for(i=0;i<color;++i)
                    if(k&(1<<i))
                        ++num;
                printf("%d\n",num);
            }
        }
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值