活用各种数据结构——线段树篇

来源:互联网 发布:中国经济现状2017数据 编辑:程序博客网 时间:2024/05/17 09:46

对《挑战程序设计竞赛》的一个记录

第三章 出类拔萃——中级篇

3.3活用各种数据结构——线段树篇

下一篇:3.3活用各种数据结构——RMQ/树状数组/分桶法和平方分割

线段树

主要还是看胡浩的文章 (完全版线段树)

- 单点更新

以下代码块的头文件”head.h” 的代码如下,不再重复黏贴

#include "cstdlib"#include "cctype"#include "cstring"#include "cstdio"#include "cmath"#include "algorithm"#include "vector"#include "string"#include "iostream"#include "sstream"#include "set"#include "queue"#include "stack"#include "fstream"#include "iomanip"#include "bitset"#include "list"#include "strstream"#include "ctime"using namespace std;typedef long long LL;typedef unsigned long long ULL;#define CC(m,what)      memset(m,what,sizeof(m))#define FOR(i,a,b)      for( int i = (a) ; i < (b) ; i ++ )#define FF(i,a)         for( int i = 0 ; i < (a) ; i ++ )#define FFD(i,a)        for( int i = (a)-1 ; i >= 0 ; i --)#define SS(a)           scanf("%d",&a)#define LL(a)           ((a)<<1)#define RR(a)           (((a)<<1)+1)#define SZ(a)           ((int)a.size())#define PP(n,m,a)       puts("---");FF(i,n){FF(j,m)cout << a[i][j] << ' ';puts("");}#define sf scanf#define pf printfconst double eps = 1e-11;const double Pi = acos(-1.0);#define read            freopen("in.txt","r",stdin)#define write           freopen("out.txt","w",stdout)#define two(x)          ((LL)1<<(x))#define include(a,b)        (((a)&(b))==(b))template<class T> inline T countbit(T n)    {return n?1+countbit(n&(n-1)):0;}template<class T> inline T sqr(T a) {return a*a;}template<class T> inline void checkmin(T &a,T b)    {if(a == -1 || a > b)a = b;}template<class T> inline void checkmax(T &a,T b)    {if(a < b)  a = b;}int dx[] = {-1,0,1,0};//up Right down Leftint dy[] = {0,1,0,-1};

hdu 1166 敌兵布阵

#include "head.h"#define lson l,m,rt<<1#define rson m+1,r,rt<<1 | 1const int Maxn = 50010;int sum[Maxn<<2];void PushUp(int rt){    sum[rt] = sum[rt<<1] + sum[rt<<1 | 1];}void Build(int l,int r,int rt){    if(l == r)    {        sf("%d",&sum[rt]);        return;    }    int m = (l + r) / 2;    Build(lson);    Build(rson);    PushUp(rt);}int Query(int L,int R,int l,int r,int rt){    if(L <= l && r <= R)    {        return sum[rt];    }    int m = (l + r) / 2;    int ret = 0;    if(L <= m) ret += Query(L,R,lson);    if(m < R) ret += Query(L,R,rson);    return ret;}void Update(int p,int k,int l,int r,int rt){    if(l == r)    {        sum[rt] += k;        return;    }    int m = (l + r) / 2;    if(p <= m) Update(p,k,lson);    if(m < p) Update(p,k,rson);    PushUp(rt);}int main(){    int T,n,l,r;    char s[10];    sf("%d",&T);    FOR(cas,1,T+1)    {        sf("%d",&n);        Build(1,n,1);        pf("Case %d:\n",cas);        while(sf("%s",s) && s[0] != 'E')        {            sf("%d%d",&l,&r);            if(s[0] == 'Q') pf("%d\n",Query(l,r,1,n,1));            else if(s[0] == 'A') Update(l,r,1,n,1);            else Update(l,-r,1,n,1);        }    }    return 0;}

hdu1754 I Hate It

#include "head.h"#define lson l,m,rt<<1#define rson m+1,r,rt<<1 | 1const int Maxn = 200000;int sum[Maxn<<2];void PushUp(int rt){    sum[rt] = max(sum[rt<<1] ,sum[rt<<1 | 1]);}void Build(int l,int r,int rt){    if(l == r)    {        sf("%d",&sum[rt]);        return;    }    int m = (l + r) / 2;    Build(lson);    Build(rson);    PushUp(rt);}int Query(int L,int R,int l,int r,int rt){    if(L <= l && r <= R)    {        return sum[rt];    }    int m = (l + r) / 2;    int ret = 0;    if(L <= m) ret = max(ret,Query(L,R,lson));    if(m < R) ret = max(ret,Query(L,R,rson));    return ret;}void Update(int p,int k,int l,int r,int rt){    if(l == r)    {        sum[rt] = k;        return;    }    int m = (l + r) / 2;    if(p <= m) Update(p,k,lson);    if(m < p) Update(p,k,rson);    PushUp(rt);}int main(){    int n,m,l,r;    char s[10];    while(~sf("%d%d",&n,&m))    {        Build(1,n,1);        FF(i,m)        {            sf("%s%d%d",s,&l,&r);            if(s[0] == 'Q') pf("%d\n",Query(l,r,1,n,1));            else Update(l,r,1,n,1);        }    }    return 0;}

hdu 1394 Minimum Inversion Number
只要用线段树先求出最初数列的最小逆序数,之后数列的逆序数可根据前一个数列的逆序数得到。

怎么用线段树求最小逆序数呢?
给出数列:1 3 6 9 0 8 5 7 4 2
线段树的区间存储的是位于该区间内的数出现过几个。例如,当遍历到0时,先检查比0大的数1-9已经有几个出现过了,Query(L,R,l,r,rt)即为Query(1,9,0,9,1)。得到的个数即为增加的逆序数个数。因此,每遍历一个数,都先查一下比它大的数有几个出现过了,然后更新当前数到对应的区间中。

初始数列的逆序数(ans)得到后,后续数列的逆序数比较好求了,例如样例中后一个数列为
3 6 9 0 8 5 7 4 2 1
此数列的逆序数个数为:
ans = ans - 原来是逆序的(比x[i]小的) + 原来不是逆序(比x[i]大的)
= ans - x[ i ] + (n - x[ i ] - 1)

#include "head.h"#define lson l,m,rt<<1#define rson m+1,r,rt<<1 | 1const int Maxn = 5010;int sum[Maxn<<2];int a[Maxn];void Build(int l,int r,int rt){    sum[rt] = 0;    if(l == r)        return ;    int m = (l + r) / 2;    Build(lson);    Build(rson);}int Query(int L,int R,int l,int r,int rt){    if(L <= l && r <= R)    {        return sum[rt];    }    int ret = 0;    int m = (l + r) / 2;    if(L <= m) ret += Query(L,R,lson);    if(m < R) ret += Query(L,R,rson);    return ret;}void Update(int p,int l,int r,int rt){    if(l == r)    {        sum[rt] ++;        return ;    }    int m = (l + r) / 2;    if(p <= m) Update(p,lson);    if(m < p) Update(p,rson);    sum[rt] ++;}int main(){    int n;    while(~sf("%d",&n))    {        Build(0,n - 1 ,1);        int ans = 0;        FF(i,n)        {            sf("%d",&a[i]);            ans += Query(a[i] + 1,n - 1,0, n - 1, 1);//查找比当前值大的            Update(a[i],0, n - 1,1);//更新当前值到对应的区间        }        int Min = ans;        FF(i,n)        {            ans += n - 1 - 2 * a[i];            Min = min(Min,ans);        }        pf("%d\n",Min);    }    return 0;}

hdu 2795 billboard
h*w的木板,每次放入1wi的木板,从上往下依次找第一个能放下该木板的位置放入木板,问该木板能放在第几行?
思路:求区间最大值,每次找到能放入木板的最左区间即可。

#include "head.h"#define lson l,m,rt<<1#define rson m + 1,r,rt<<1 | 1const int Maxn =200010;int sum[Maxn<<2],h,w,n;void PushUp(int rt){    sum[rt] = max(sum[rt << 1] , sum[rt << 1 | 1]);}void Build(int l,int r,int rt){    sum[rt] = w;    if(l == r) return;    int m = (l +r) / 2;    Build(lson);    Build(rson);}void Query(int key,int l,int r, int rt,int &flag){    if(key > sum[rt])    {        flag = -1;        return;    }    if(l == r)    {        flag = l;        sum[rt] -= key;        return;    }    int m = (l + r) / 2;    if(sum[rt<<1] >= key)        Query(key,lson,flag);    else if(sum[rt<<1 | 1] >= key)        Query(key,rson,flag);    PushUp(rt);}int main(){    int key,flag;    while(~sf("%d%d%d",&h,&w,&n))    {        Build(1,n,1);        FF(i,n)        {            sf("%d",&key);            flag = 0;            Query(key,1,min(h,n),1,flag);            pf("%d\n",flag);        }    }    return 0;}

POJ 2828 Buy Tickets
题意:排队买票,买票的过程中一直有人在插队,给出每个人要插入的位置和val值,求最终的队伍中从前往后每个人的val。
思路:区间保存剩余的空位置数。如果空位置数要插入的位置,则进入该区间。由于越是后来的插入的优先级越高(即越有可能插入到想插的位置),所以从后向前遍历。
代码如下:

#include "head.h"#define lson l,m,rt<<1#define rson m + 1,r,rt<<1 | 1const int Maxn = 200010;int sum[Maxn << 2];int val[Maxn];int pos[Maxn][2];void PushUp(int rt){    sum[rt] = sum[rt<<1] + sum[rt<< 1 | 1];}void Build(int l,int r,int rt){    if(l == r)    {        sum[rt] = 1;        return ;    }    int m = (l + r) / 2;    Build(lson);    Build(rson);    PushUp(rt);}void Update(int key,int p,int l,int r,int rt){    if(l == r)    {        sum[rt] --;        val[l] = p;        return;    }    int m = (l + r) / 2;    if(sum[rt<<1]>=key)        Update(key,p,lson);    else        Update(key - sum[rt<<1] ,p,rson);    PushUp(rt);}int main(){    int n;    while(~sf("%d",&n))    {        Build(0,n - 1,1);        FF(i,n)            sf("%d%d",&pos[i][0],&pos[i][1]);        FFD(i,n)            Update(pos[i][0] + 1,pos[i][1],0,n - 1, 1);        FF(i,n)            pf("%d%c",val[i],i + 1 == n?'\n':' ');    }    return 0;}

poj 2886 Who Gets the Most Candies?

题意:一群小朋友顺时针方向围城一圈,每个人手里都有一个值a,一开始从第k个人开始出局,下一个出局的由当前出局人手里的值来决定。如果a为正数,则顺时针数a个数,第a个人出局,如果是负数,则逆时针数,直到游戏结束。已知,当某人是第p个出局时,他将会达到val[p]的值,val[p]为p的因子个数。求获得因子个数最大的值是哪个人,并且这个人获得的val值。
思路:线段树区间保存该区间内未出局的人的个数。即求区间的和,一开始每个位置都为1,出局一个人则为0。
如果第k个人出局,他手上的值为a。
a>0,计算区间[k + 1,n]的和total,如果total>=a,则下一个要出局的点在[k + 1,n]中,否则在[1,k-1]中。
a<0时,也一样的思路。
因此,主要做法就是区间查询加单点更新。

我是直接暴力求因子个数,来一个数求一次。也可以考虑用反素数的算法首先将n范围内因子个数最大的那个值求出来,这样就不用每次都算因子个数。
代码如下:

#include "head.h"#define lson l,m,rt<<1#define rson m + 1,r,rt<<1 | 1const int Maxn = 500010;int sum[Maxn << 2];char s[Maxn][15];int num[Maxn];int prime[Maxn],flag[Maxn];int cnt;//初始化得到素数void getPrime(){    for(int i = 2;i < Maxn;i ++)    {        if(flag[i] == 0)        {            prime[cnt ++] = i;            for(int j = i * 2;j < Maxn;j += i)                flag[j] = 1;        }    }}//求因子个数int getDivisor(int n){    int ans = 1;    for(int i = 0;prime[i] * prime[i] <= n;i ++)    {        int tmp = 0;        if(n % prime[i] == 0)        {            while(n % prime[i] == 0)            {                tmp ++;                n /= prime[i];            }            ans = ans * (tmp + 1);        }    }    if(n != 1)        ans *= 2;    return ans;}void PushUp(int rt){    sum[rt] = sum[rt<<1] + sum[rt<< 1 | 1];}void Build(int l,int r,int rt){    if(l == r)    {        sum[rt] = 1;        return ;    }    int m = (l + r) / 2;    Build(lson);    Build(rson);    PushUp(rt);}int Query(int L,int R,int l,int r,int rt){    if(L <= l && r <= R)    {        return sum[rt];    }    int m = (l + r) / 2;    int ret = 0;    if(L <= m) ret += Query(L,R,lson);    if(m < R) ret += Query(L,R,rson);    return ret;}int Update(int p,int l,int r,int rt){    if(l == r)    {        sum[rt] --;        return l;    }    int m = (l + r) / 2;    int ret ;    if(sum[rt<<1] >= p) ret = Update(p,lson);    else ret = Update(p - sum[rt<<1],rson);    PushUp(rt);    return ret;}int main(){    getPrime();    int n,k;    while(~sf("%d%d",&n,&k))    {        Build(1,n ,1);        for(int i = 1;i <= n;i ++)            sf("%s%d",s[i],&num[i]);        int total = 0,Max,pos=k;        Update(k,1,n,1);        Max = getDivisor(1);        for(int i = n - 1;i > 0;i --)        {            if(num[k] < 0)//逆时针转            {                total = (k <=1)?0:Query(1,k - 1,1,n,1);                if(total >= -num[k])k = total + num[k] + 1;//[1,k -1]中的个数>= |num[k]|,所以要出局的数在[1,k-1]中,具体位置为k的值                else k =total + 1 + i + num[k];//[1,k -1]中的个数< |num[k]|,需要更新的位置为k,在[k+1,n]区间中            }            else//顺时针转            {                total = (k >= n)?0:Query(k + 1,n,1,n,1);                if(total >= num[k]) k = i - total + num[k];                else k = num[k] - total;            }            k = (k % i) ;            if(k <= 0) k += i;            k = Update(k,1,n,1);//k位置的人出局,更新区间和            int tmp = getDivisor(n - i + 1);            if(tmp > Max)                Max = tmp,pos = k;        }        pf("%s %d\n",s[pos],Max);    }    return 0;}

- 成段更新

hdu 1698 Just a Hook
题意:给定n,初始是1-n位置的值全为1,给出Q个操作,每个操作x,y,z,表示将区间[x,y]的值全设为z,z的取值为1,2,3。求最后区间[1,n]的和。
思路:线段树成段更新,区间保存和。
代码如下:

#include "head.h"#define lson l,m,rt<<1#define rson m + 1,r,rt<<1 | 1const int Maxn = 100010;int sum[Maxn << 2];int col[Maxn << 2];void PushUp(int rt){    sum[rt] = sum[rt<<1] + sum[rt<<1 | 1];}void PushDown(int rt,int m){    if(col[rt])    {        col[rt<<1] = col[rt<<1 | 1] = col[rt];        sum[rt<<1] = (m - (m>>1)) * col[rt];        sum[rt<<1 | 1] = (m >> 1) * col[rt];        col[rt] = 0;    }}void Build(int l,int r,int rt){    col[rt] = 0;    if(l == r)    {        sum[rt] = 1;        return ;    }    int m = (l + r) / 2;    Build(lson);    Build(rson);    PushUp(rt);}void Update(int L,int R,int k,int l,int r,int rt){    if(L <= l && r <= R)    {        col[rt] = k;        sum[rt] = (r - l + 1) * k;        return;    }    PushDown(rt,r - l + 1);    int m = (l + r) / 2;    if(L <= m) Update(L,R,k,lson);    if(m < R) Update(L,R,k,rson);    PushUp(rt);}int main(){    int T,n,Q,x,y,z;    sf("%d",&T);    FOR(cas,1,T+1)    {        sf("%d",&n);        Build(1,n,1);        sf("%d",&Q);        FF(i,Q)        {            sf("%d%d%d",&x,&y,&z);            Update(x,y,z,1,n,1);        }        pf("Case %d: The total value of the hook is %d.\n",cas,sum[1]);    }    return 0;}

poj 3468 A Simple Problem with Integers
题意:给出n个数和Q个操作,操作如下:
C a b c:将[a, b]区间中的每个数加上c。
Q a b: 计算[a, b ]区间内的数值之和。
思路:线段树区间更新,区间求和。col[]保存当前区间中每个数需要加的值。当遍历到区间[a, b]时,如果还需往下遍历,则先把[a,(a+b)/2]和[(a + b) / 2+1,b]区间中的col更新(加上[a,b]区间中的col)。由于是懒更新,因此在查询时也需要更新col的值。
代码如下:

#define lson l,m,rt<<1#define rson m + 1,r,rt<<1 | 1const int Maxn = 100010;LL sum[Maxn << 2];LL col[Maxn << 2];void PushUp(int rt){    sum[rt] = sum[rt<<1] + sum[rt<<1 | 1];}void PushDown(int rt,int m){    if(col[rt])    {        col[rt<<1] += col[rt];        col[rt<<1 | 1] += col[rt];        sum[rt<<1] += (m - (m>>1)) * col[rt];        sum[rt<<1 | 1] += (m >> 1) * col[rt];        col[rt] = 0;    }}void Build(int l,int r,int rt){    col[rt] = 0;    if(l == r)    {        sf("%lld",&sum[rt]);        return ;    }    int m = (l + r) / 2;    Build(lson);    Build(rson);    PushUp(rt);}void Update(int L,int R,int k,int l,int r,int rt){    if(L <= l && r <= R)    {        col[rt] += k;        sum[rt] += (LL)(r - l + 1) * k;        return;    }    PushDown(rt,r - l + 1);    int m = (l + r) / 2;    if(L <= m) Update(L,R,k,lson);    if(m < R) Update(L,R,k,rson);    PushUp(rt);}LL Query(int L,int R,int l,int r,int rt){    if(L <= l && r <= R)    {        return sum[rt];    }    PushDown(rt,r - l + 1);    LL ret = 0;    int m = (l + r) >> 1;    if(L <= m) ret += Query(L,R,lson);    if(m < R) ret += Query(L,R,rson);    return ret;}int main(){    int n,Q,x,y,z;    char s[5];    while(~sf("%d%d",&n,&Q))    {        Build(1,n,1);        FF(i,Q)        {            sf("%s%d%d",s,&x,&y);            if(s[0] == 'Q')                pf("%lld\n",Query(x,y,1,n,1));            else            {                sf("%d",&z);                Update(x,y,z,1,n,1);            }        }    }    return 0;}

poj 2528 Mayor’s posters

题意:在墙上贴海报,海报之间可以相互重叠,问最后能看到几张海报。
思路:海报的区间范围很大,但是海报总数不大,如果之间用海报的区间范围会超时,超内存,所以进行离散化。离散化的时候要注意:
由于给定的是线段而不是点,因而需要注意重叠部分,普通的离散化会有问题:
例子1:[1,10],[1,4],[6,10]
例子2:[1,10],[1,4],[5,10]
例子1,2的离散化都为[1,4],[1,2],[3,4],但是,例子1中线段1不会被完全覆盖,例子2中线段1被完全覆盖,两者结果是不一样的,所以要对离散化进行调整。在相邻两个数间距大于1时添加一个数。例如[1,2,6,10],调整成[1,2,5,6,9,10]。然后再用线段树进行处理。这里线段树的区间只要保存最近更新的数是多少就好了。
代码如下:

#define lson l,m,rt<<1#define rson m + 1,r,rt<<1 | 1const int Maxn = 500010;int col[Maxn << 2];int s[Maxn],e[Maxn],x[Maxn];set<int> res;void PushDown(int rt,int m){    if(col[rt])    {        col[rt << 1] = col[rt << 1 | 1] = col[rt];        col[rt] = 0;    }}void Build(int l,int r,int rt){    col[rt] = 0;    if(l == r) return;    int m = (l +r) / 2;    Build(lson);    Build(rson);}void Update(int L,int R,int c,int l,int r,int rt){    if(L <= l && r <= R)    {        col[rt] = c;        return ;    }    PushDown(rt,col[rt]);    int m = (l + r) >> 1;    if(L <= m) Update(L,R,c,lson);    if(m < R) Update(L,R,c,rson);}void Query(int l,int r,int rt){    if(l == r)    {        if(col[rt] != 0)            res.insert(col[rt]);        return;    }    PushDown(rt,col[rt]);    int m = (l + r) >> 1;    Query(lson);    Query(rson);}int binarySort(int l,int r,int k){    while(l <= r)    {        int mid = (l + r) >> 1;        if(x[mid] < k)            l = mid + 1;        else if(x[mid] > k)            r = mid - 1;        else            return mid;    }}int main(){    int T,n;    sf("%d",&T);    while(T --)    {        sf("%d",&n);        int cnt = 0,m = 1;        for(int i = 0;i < n;i ++)        {            sf("%d%d",&s[i],&e[i]);            x[cnt ++] = s[i];            x[cnt ++] = e[i];        }        sort(x,x + cnt);        for(int i = 1;i < cnt;i ++)            if(x[i] != x[i - 1]) x[m ++] = x[i];        for(int i = m - 1; i > 0;i --)            if(x[i] != x[i - 1] + 1) x[m ++] = x[i] - 1;        sort(x,x + m);        Build(0,m - 1,1);        for(int i = 0;i < n;i ++)        {            int l = binarySort(0,m - 1,s[i]);            int r = binarySort(0,m - 1,e[i]);            Update(l,r,i + 1,0,m - 1,1);        }        Query(0,m - 1, 1);        pf("%d\n",res.size());        res.clear();    }    return 0;}

poj 3225 Help with Intervals
题意:给定一个空集,经过一系列集合操作后,求最后剩下的集合。注意集合里的数是有理数,不是整数。因此[1,5]减去[3,3]后剩下的是[1,3)(3,5]
集合操作如下:
U [a,b]:[a,b]之间的数都置1
D [a,b]:[a,b]之间的数都置0
S [a,b]:[a,b]之间的数取反,即异或1
C [a,b]:[a,b]之间的数取反,[,a-1],[b+1,+]之间的数置0
I [a,b]:保留[a,b]之间的数,[,a-1],[b+1,+]之间的数置0
思路:因为这里涉及到开区间和闭区间,所以可以把范围扩大两倍,例如[2,4]对应[4,8],(2,4]对应[5,8],[2,4)对应[4,7],(2,4)对应[3,7]。最后的结果也可根据位置的奇偶性来判断是开区间还是闭区间。

该题注意a,b的范围是包含0的,不要漏了这个点,还有update时L>R时不要再往下update,会RE。
由于给定的区间数据范围不是很大,因此没有去做离散化或者求出给定数据中的最大值来初始化线段树,而是直接用了Maxn,这一部分继续优化。
代码如下:

#include "head.h"#define lson l,m,rt<<1#define rson m + 1,r,rt<<1 | 1const int Maxn = 200010;int col[Maxn << 2];int XOR[Maxn << 2];int ans[Maxn];void PushDown(int rt){    if(col[rt] != -1)    {        col[rt << 1] = col[rt << 1 | 1] = col[rt];        XOR[rt << 1] = XOR[rt << 1 | 1] = XOR[rt];        col[rt] = -1;        XOR[rt] = 0;    }    if(XOR[rt] == 1)    {        XOR[rt << 1] ^=  XOR[rt];        XOR[rt << 1 | 1] ^= XOR[rt];        XOR[rt] = 0;    }}void Build(int l,int r,int rt){    col[rt] = -1;    XOR[rt] = 0;    if(l == r) return;    int m = (l + r) >> 1;    Build(lson);    Build(rson);}void Update(char op,int L,int R,int l,int r,int rt){    if(L > R) return;    if(L <= l && r <= R)    {        if(op == 'U')            col[rt] = 1,XOR[rt] = 0;        else if(op == 'D')            col[rt] = 0,XOR[rt] = 0;        else            XOR[rt] ^= 1;        return;    }    PushDown(rt);    int m = (l + r) >> 1;    if(L <= m) Update(op,L,R,lson);    if(m < R) Update(op,L,R,rson);}void Query(int l,int r,int rt){    if(col[rt] != -1 || l == r)    {        for(int i = l;i <= r;i ++)        {            ans[i] = col[rt];            if(XOR[rt] != 0)                ans[i] = ans[i] == -1?1:ans[i]^XOR[rt];        }        return;    }    PushDown(rt);    int m = (l + r) >> 1;    Query(lson);    Query(rson);}int main(){    int a,b;    char op,l,r;    Build(0,Maxn,1);    while(~sf("%c %c%d,%d%c\n",&op,&l,&a,&b,&r))    {        a <<= 1;        b <<= 1;        if(l == '(')            a ++;        if(r == ')')            b --;        if(op == 'U' || op == 'D' || op == 'S')            Update(op,a,b,0,Maxn,1);        else        {            if(op == 'C')                Update('S',a,b,0,Maxn,1);            Update('D',0,a - 1,0,Maxn,1);            Update('D',b + 1,Maxn,0,Maxn,1);        }    }    Query(0,Maxn,1);    int f = 0;    for(int i = 0;i <Maxn;i ++)    {        if(ans[i] == 1)        {            f ++;            if(i & 1) l = '(';            else l = '[';            a = i >> 1;            for(int j = i + 1;j < Maxn;j ++)            {                if(ans[j] != 1)                {                    i = j - 1;                    if(i & 1) r = ')';                    else r = ']';                    b = (i + 1) >> 1;                    if(f > 1) pf(" ");                    pf("%c%d,%d%c",l,a,b,r);                    break;                }            }        }    }    if(f == 0)        pf("empty set");    pf("\n");    return 0;}

poj 1436 Horizontally Visible Segments

这题让我挂了一天,原因是看错题意了!!!!实在是对自己无语啊~~题意中两条竖线是能被一条水平线段连接且改水平线段不经过其他竖线。注意这条水平选段是如下图上半部分所示,而我之前理解的是水平直线,无限长,orz,真是智商捉急。。。
总之一句话:一定要看清题意啊。
这里写图片描述

题意:在一个平面内,有一些竖直的线段,若两条竖直线段之间可以连一条水平线段,这条水平线段不与其他竖直线段相交,称这两条竖直线段为“相互可见”的。若存在三条竖直线段,两两“相互可见”,则构成“线段三角形”。给出一些竖直的线段,问一共有多少“线段三角形”。
思路:区间覆盖,线段树区间保存该区间最近被哪条竖直线段覆盖。将竖直线段按x值从小到大排序,在遍历到一条竖直线段时,先查找一遍当前线段的区间范围内有哪些线段存在。存在的那些线段与当前线段之间都符合题意,hash一下。然后再将当前线段更新上去。

代码如下:

#include "head.h"#define lson l,m,rt<<1#define rson m + 1,r,rt<<1 | 1const int Maxn = 16010;int col[Maxn << 2];bool Hash[8010][8010];struct node{    int y1,y2,x;};node line[Maxn];void PushDown(int rt){    if(col[rt])    {        col[rt << 1] = col[rt << 1 | 1] = col[rt];        col[rt] = 0;    }}void Build(int l,int r,int rt){    col[rt] = 0;    if(l == r) return ;    int m = (l + r) >> 1;    Build(lson);    Build(rson);}void Update(int L,int R,int l,int r,int rt,int cur){    if(L <= l && r <= R)    {        col[rt] = cur;        return;    }    PushDown(rt);    int m = (l + r) >> 1;    if(L <= m) Update(L,R,lson,cur);    if(m < R) Update(L,R,rson,cur);}void Query(int L,int R,int l,int r,int rt,int cur){    if(L <= l && r <= R)    {        if(col[rt] != 0)        {            Hash[col[rt]][cur] = true;            return;        }    }    if(l == r)    {        return;    }    PushDown(rt);    int m = (l + r) >> 1;    if(L <= m) Query(L,R,lson,cur);    if(m < R) Query(L,R,rson,cur);}int cmp(node a,node b){    return a.x < b.x;}int main(){    int T,n,m;    sf("%d",&T);    while(T--)    {        sf("%d",&n);        m = 0;        for(int i = 1;i <= n;i ++)        {            sf("%d%d%d",&line[i].y1,&line[i].y2,&line[i].x);            line[i].y1 <<= 1;            line[i].y2 <<= 1;            m = max(m,line[i].y2);        }        Build(0,m,1);        sort(line + 1,line + n + 1,cmp);        for(int i = 1;i <= n;i ++)        {            Query(line[i].y1,line[i].y2,0,m,1,i);            Update(line[i].y1,line[i].y2,0,m,1,i);        }        int ans = 0;         for(int i=1;i<=n;++i){            for(int j=1;j<=n;++j){                if(Hash[i][j])                for(int k=1;k<=n;++k){                    if(Hash[i][k] && Hash[j][k])++ans;                }            }        }        pf("%d\n",ans);        memset(Hash,0,sizeof(Hash));    }    return 0;}

poj 2991 Crane

题意:有一台起重机。我们把起重机看成由N条线段依次首尾相接而成。第i条线段的长度是Li。最开始,所有的线段都笔直连接,指向上方。
有C条操纵起重机的指令。指令i给出两个整数Si,Ai,效果是使线段SiSi+1之间的角度变成Ai度。其中角度指的是从线段Si开始沿逆时针方向旋转到Si+1所经过的角度。最开始时所有的角度都是180度。
按顺序执行这C条指令。在每条指令执行之后,输出起重机的前端(第N条线段的端点)的坐标。假设起重机的支点坐标是(0,0).
已知:
1N,C10000
1Li100
1Si<N,0Ai359
思路: 这题是看了题解才知道该怎么解的。我们把每一小段都当成一个向量,端点的坐标就是向量之和。
首先来看旋转变化中点的坐标变换:
这里写图片描述

线段树区间需要保存角度的变换。每次PushDown时,先修正子区间的x,y的值,然后再加上变化的角度值。每次PushUp时,跟新根区间的x,y的值。
代码如下:

#include "head.h"#define lson l,m,rt<<1#define rson m + 1,r,rt<<1 | 1const int Maxn = 10010;typedef struct Node{    double x,y,col;};double ang[Maxn];Node node[Maxn<<2];void PushUp(int rt){    node[rt].x = node[rt << 1].x  + node[rt << 1 | 1].x ;    node[rt].y = node[rt << 1].y  + node[rt << 1 | 1].y ;}void Update_xy(int rt,double ang){    double d = ang * pi / 180;    double tmp_x = node[rt].x * cos(d) - node[rt].y * sin(d);    double tmp_y = node[rt].x * sin(d) + node[rt].y * cos(d);    node[rt].x = tmp_x;    node[rt].y = tmp_y;}void PushDown(int rt){    if(node[rt].col)    {        node[rt<<1].col += node[rt].col;        node[rt<<1 | 1].col += node[rt].col;        Update_xy(rt<<1,node[rt].col);        Update_xy(rt<<1|1,node[rt].col);        node[rt].col = 0;    }}void Build(int l,int r,int rt){    node[rt].col =0;    if(l == r)    {        sf("%lf",&node[rt].y);        node[rt].x = 0;        ang[l] = 180;        return ;    }    int m = (l + r) >> 1;    Build(lson);    Build(rson);    PushUp(rt);}void Update(int L,int R,int l,int r,int rt,double ang){    if(L <= l && r <= R)    {        node[rt].col += ang;        Update_xy(rt,ang);        return ;    }    PushDown(rt);    int m = (l + r) / 2;    if(L <= m) Update(L,R,lson,ang);    if(m < R) Update(L,R,rson,ang);    PushUp(rt);}int main(){    int n,m;    int s,f = 0;    double t;    while(~sf("%d%d",&n,&m))    {        Build(1,n,1);        if(f > 0)            pf("\n");        f++;        ang[0] = 180;        for(int i = 0;i < m;i ++)        {            sf("%d%lf",&s,&t);            Update(s + 1,n,1,n,1,t - ang[s]);            ang[s] = t;            pf("%.2lf %.2lf\n",node[1].x,node[1].y);        }    }    return 0;}

- 区间合并

poj 3667 Hotel
题意:订房间。每次订房间,若有x个人,就要分连续的x间房给他,如果有多种分法,就要从序号最小的开始分。
1 a:询问是不是有连续长度为a的空房间,若有,则住进最左边。
2 a b:将[a,a+b-1]的房间清空。
思路:由于要查询连续最长空位,因此区间需要记录这个区间中的连续最长空位是(msum[rt])多少。这个最长空位怎么计算?很明显,一是来自左节点的最长空位(msum[rt<<1]),另外一个来自右节点的最长空位(msum[rt<<1|1]),还有一部分来自左节点的从右端开始最长可用空位(rsum[rt<<1])+右节点的从左端开始的最长可用空位(lsum[rt<<1|1])。
具体例子来说,例如
区间[1,5]对应1,0,0,01,则lsum[] = 0,rsum[] = 0 ,msum[]= 3
区间[1,5]对应1,0,0,0,0,则lsum[] = 0,rsum[] = 4 ,msum[]= 4

由上面的分析,可知区间中需要保存3个值,一个是从最左端开始可用的连续最长空位 lsum,一个是从右端开始可用的连续最长空位 rsum,还有一个区间中连续最长空位。

代码如下:

#include "head.h"#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1const int Maxn = 50010;int lsum[Maxn<<2],msum[Maxn<<2],rsum[Maxn<<2];int col[Maxn<<2];void PushUp(int rt,int m){    lsum[rt] = lsum[rt<<1] + ((lsum[rt<<1] == (m - (m>>1)))?lsum[rt<<1|1]:0);    rsum[rt] = rsum[rt<<1|1] + ((rsum[rt<<1|1] == (m>>1))?rsum[rt<<1]:0);    msum[rt] = max(rsum[rt<<1] + lsum[rt<<1|1],max(msum[rt<<1],msum[rt<<1|1]));}void PushDown(int rt){    if(col[rt])    {        lsum[rt<<1] = rsum[rt<<1] = msum[rt<<1] = lsum[rt] - (lsum[rt]>>1);        lsum[rt<<1|1] = rsum[rt<<1|1] = msum[rt<<1|1] = (lsum[rt]>>1);        col[rt<<1] = col[rt<<1|1] = col[rt];        col[rt] = 0;    }}void Build(int l,int r,int rt){    col[rt] = 0;    if(l == r)    {        lsum[rt] = rsum[rt] = msum[rt] = 1;        return;    }    int m = (l + r) >> 1;    Build(lson);    Build(rson);    PushUp(rt,r - l + 1);}void Update(int L,int R,int c,int l,int r,int rt){    if(L <= l && r <= R)    {        lsum[rt] = rsum[rt] = msum[rt] = (c == 0)?r-l+1:0;        col[rt] = 1;        return ;    }    PushDown(rt);    int m = (l + r) >> 1;    if(L <= m) Update(L,R,c,lson);    if(m < R) Update(L,R,c,rson);    PushUp(rt,r-l+1);}int Query(int c,int l,int r,int rt){    int m = (l + r) >> 1;    if(msum[rt] >= c)    {        if(lsum[rt] >= c) return l;        if(msum[rt<<1] >= c) Query(c,lson);        else if(rsum[rt<<1] + lsum[rt<<1|1] >=c && rsum[rt<<1] != 0) return m - rsum[rt<<1] + 1;        else if(msum[rt<<1|1] >=c) Query(c,rson);    }    else        return 0;}int main(){    int n,m;    int c,x,y;    while(~sf("%d%d",&n,&m))    {        Build(1,n,1);        for(int i = 0;i < m;i ++)        {            sf("%d%d",&c,&x);            if(c == 1)            {                int ans = Query(x,1,n,1);                pf("%d\n",ans);                if(ans != 0) Update(ans,ans + x - 1,1,1,n,1);            }            else            {                sf("%d",&y);                Update(x,x + y - 1,0,1,n,1);            }        }    }    return 0;}

hdu 3308 LCIS
题意:求给定区间中的最长连续上升子序列的长度。
思路:这题跟hotel有点相似,lsum[]保存区间从左到右最长上升子序列长度,rsum[]保存区间从右到左最长上升子序列长度,msum[]保存区间中最长上升子序列长度。
当左子树的最右边的值>=右子树最左边的的值时,
msum[rt] = max(msum[rt<<1],msum[rt<<1|1])
否则,两边还可以进行合并
msum[rt] = max(rsum[rt<<1]+lsum[rt<<1|1],max(msum[rt<<1],msum[rt<<1|1])

代码如下:

#define "head.h"#define lson l,m,rt<<1#define rson m + 1,r,rt<<1|1const int Maxn = 100010;int lsum[Maxn<<2],rsum[Maxn<<2],msum[Maxn<<2];int x[Maxn];int ans;void PushUp(int rt,int l,int r){    lsum[rt] = lsum[rt<<1];    rsum[rt] = rsum[rt<<1|1];    msum[rt] = max(msum[rt<<1],msum[rt<<1|1]);    int m = (l + r) >> 1;    if(x[m] < x[m + 1])    {        if(lsum[rt<<1] == m - l + 1)            lsum[rt] += lsum[rt<<1|1];        if(rsum[rt<<1|1] == r - m)            rsum[rt] += rsum[rt<<1];        msum[rt] = max(msum[rt],rsum[rt<<1] + lsum[rt<<1|1]);    }}void Build(int l,int r,int rt){    if(l == r)    {        sf("%d",&x[l]);        lsum[rt] = rsum[rt] = msum[rt] = 1;        return;    }    int m = (l + r) >> 1;    Build(lson);    Build(rson);    PushUp(rt,l,r);}void Update(int p,int c,int l,int r,int rt){    if(l == r)    {        x[p] = c;        return ;    }    int m = (l + r) >> 1;    if(p<=m) Update(p,c,lson);    if(p>m) Update(p,c,rson);    PushUp(rt,l,r);}int Query(int L,int R,int l,int r,int rt){    if(L<= l && r<= R)    {        return msum[rt];    }    int ret= 0;    int m = (l + r) >> 1;    if(L <= m) ret = max(ret,Query(L,R,lson));    if(m < R) ret = max(ret,Query(L,R,rson));    if(x[m] < x[m + 1])         ret = max( ret, min( m - L + 1, rsum[rt<<1]) + min(R - m, lsum[rt<<1|1]) );    return ret;}int main(){    int T,n,m,x,y;    char s[5];    sf("%d",&T);    while(T--)    {        sf("%d%d",&n,&m);        Build(0,n - 1,1);        while(m --)        {            sf("%s%d%d",s,&x,&y);            if(s[0]=='Q')            {                ans = Query(x,y,0,n - 1, 1);                pf("%d\n",ans);            }            else                Update(x,y,0,n - 1,1);        }    }    return 0;}

hdu 3397 Sequence operation
这题就是前两题的综合。写的我要恶心了。。。
题意:中文题。。
思路:关于区间置1和区间置0跟hotel那题差不多,主要考虑区间置异或。例如[0 0 1 0 0],此时lsum[] = 0,rsum[] = 0,异或的时候各个数取反,为[1 1 0 1 1],lsum[] = 2,rsum[] = 2。因此一开始只保留区间内连续为1的子串的长度是不够的,还要保留区间内连续为0的子串的长度。

代码如下:

#include "head.h"#define lson l,m,rt<<1#define rson m + 1,r,rt<<1|1#define mid int m = (l + r) >> 1const int Maxn = 100010;int sum[Maxn<<2],lsum[Maxn<<2],rsum[Maxn<<2],msum[Maxn<<2],col[Maxn<<2];//维护1int revlsum[Maxn<<2],revrsum[Maxn<<2],revmsum[Maxn<<2];//维护0void PushUp(int rt,int l,int r){    sum[rt] = sum[rt<<1] + sum[rt<<1|1];    lsum[rt] = lsum[rt<<1];    rsum[rt] = rsum[rt<<1|1];    msum[rt] = max(msum[rt<<1],msum[rt<<1|1]);    revlsum[rt] = revlsum[rt<<1];    revrsum[rt] = revrsum[rt<<1|1];    revmsum[rt] = max(revmsum[rt<<1],revmsum[rt<<1|1]);    mid;    if(lsum[rt<<1] == m - l + 1 && lsum[rt<<1|1] !=0) lsum[rt] += lsum[rt<<1|1];    if(rsum[rt<<1|1] == r - m && rsum[rt<<1] != 0) rsum[rt] += rsum[rt<<1];    if(rsum[rt<<1] > 0 && lsum[rt<<1|1] > 0)        msum[rt] = max(msum[rt],rsum[rt<<1] + lsum[rt<<1|1]);    if(revlsum[rt<<1] == m - l + 1 && revlsum[rt<<1|1] != 0) revlsum[rt] += revlsum[rt<<1|1];    if(revrsum[rt<<1|1] == r - m && revrsum[rt<<1] != 0) revrsum[rt] += revrsum[rt<<1];    if(revrsum[rt<<1] > 0 && revlsum[rt<<1|1] > 0)        revmsum[rt] = max(revmsum[rt],revrsum[rt<<1] + revlsum[rt<<1|1]);}void solved(int rt,int c,int m) //如果是异或操作{    if(c == 1)//如果原来区间col[rt]= 1,则异或操作后原来区间内的值全置0    {        lsum[rt] = rsum[rt] = msum[rt] = sum[rt] = 0;        revlsum[rt] = revrsum[rt] = revmsum[rt] = m;        col[rt] = 0;    }    else if (c == 0)//如果原来区间col[rt]= 0,则异或操作后原来区间内的值全置1    {        lsum[rt] = rsum[rt] = msum[rt] = sum[rt] = m;        revlsum[rt] = revrsum[rt] = revmsum[rt] = 0;        col[rt] = 1;    }    else//如果原来区间并非全置0或置1过,则各个数取反,维护1的最长子串跟维护2的最长子串需要调换    {        col[rt] = col[rt] == 2?-1:2;        sum[rt] = m - sum[rt];        swap(lsum[rt],revlsum[rt]);        swap(rsum[rt],revrsum[rt]);        swap(msum[rt],revmsum[rt]);    }}void PushDown(int rt,int l,int r){    if(col[rt] != -1)    {        mid;        if(col[rt] != 2)        {            sum[rt<<1] = col[rt]==1?(m - l + 1):0;            sum[rt<<1|1] = col[rt] == 1? (r - m):0;            lsum[rt<<1] = rsum[rt<<1] = msum[rt<<1] = sum[rt<<1];            lsum[rt<<1|1] = rsum[rt<<1|1] = msum[rt<<1|1] = sum[rt<<1|1];            revlsum[rt<<1] = revrsum[rt<<1] = revmsum[rt<<1] = m - l + 1 -sum[rt<<1];            revlsum[rt<<1|1] = revrsum[rt<<1|1] = revmsum[rt<<1|1] = r - m - sum[rt<<1|1];            col[rt<<1] = col[rt<<1|1] = col[rt];        }        else        {            solved(rt<<1,col[rt<<1],m - l + 1);            solved(rt<<1|1,col[rt<<1|1],r - m);        }        col[rt] = -1;    }}void Build(int l,int r,int rt){    sum[rt] = 0;    col[rt] = -1;    if(l == r)    {        sf("%d",&sum[rt]);        lsum[rt] = rsum[rt] = msum[rt] = sum[rt];        revlsum[rt] = revrsum[rt] = revmsum[rt] = sum[rt] ^ 1;        return ;    }    mid;    Build(lson);    Build(rson);    PushUp(rt,l,r);}void Update(int L,int R,int c,int l,int r,int rt){    if(L <= l && r <= R)    {        if(c != 2)        {            sum[rt] = c == 1?r - l + 1:0;            lsum[rt] = rsum[rt] = msum[rt] = sum[rt];            revlsum[rt] = revrsum[rt] = revmsum[rt] = r - l + 1 - sum[rt];            col[rt] = c;        }        else        {            solved(rt,col[rt],r - l + 1);        }        return ;    }    mid;    PushDown(rt,l,r);    if(L <= m) Update(L,R,c,lson);    if(m < R) Update(L,R,c,rson);    PushUp(rt,l,r);}int Query(int L,int R,int l,int r,int rt){    if(L <= l && r <= R)    {        return sum[rt];    }    PushDown(rt,l,r);    int ret = 0;    mid;    if(L <= m) ret += Query(L,R,lson);    if(m < R) ret += Query(L,R,rson);    return ret;}int Query1(int L,int R,int l,int r,int rt){    if(L <= l && r <= R)    {        return msum[rt];    }    mid;    PushDown(rt,l,r);    int ret = 0;    if(L <= m) ret = max(ret,Query1(L,R,lson));    if(m < R) ret = max(ret,Query1(L,R,rson));    if(rsum[rt<<1] > 0 && lsum[rt<<1|1] > 0)        ret = max(ret,min(rsum[rt<<1],m - L + 1) + min(lsum[rt<<1|1],R-m));    return ret;}int main(){    int T,n,m,c,x,y;    sf("%d",&T);    while(T--)    {        sf("%d%d",&n,&m);        Build(0,n - 1, 1);        for(int i = 0;i < m;i ++)        {            sf("%d%d%d",&c,&x,&y);            if(c <= 2) Update(x,y,c,0,n - 1,1);            else if(c == 3) pf("%d\n",Query(x,y,0,n - 1,1));            else pf("%d\n",Query1(x,y,0,n - 1,1));        }    }    return 0;}

hdu 2871 Memory Control

hdu 1540 Tunnel Warfare

- 扫描线

hdu 1542 Atlantis
题意:有一些长方形(可重叠),计算被长方形覆盖到的面积。
思路:扫描线,可参考这个博客
要注意的是线段树中的节点不是指某个点的值,而是一段线段的长度。比如区间[0,2]表示线段端点为0到3的线段长度。
因为长方形个数不多,而顶点值的范围比较大,可以考虑离散化,节省很多空间。
代码如下:

#include "head.h"#define lson l,m,rt<<1#define rson m + 1,r,rt<<1|1#define mid int m = (l + r) >> 1const int Maxn =250;struct seg{    double l,r,h;    int f;    seg(){}    seg(double a,double b,double c,int d):l(a),r(b),h(c),f(d){}    bool operator < (const seg &cmp) const {        return h < cmp.h;    }}ss[Maxn];int col[Maxn<<2];double sum[Maxn<<2], p[Maxn];//p用来存顶点出现过的值,用来离散化的时候查找int Binary(double key,int l,int r)//查找值所在位置,代表离散化的值{    while(l <= r)    {        int m = (l + r) >> 1;        if(key == p[m]) return m;        else if(key < p[m]) r = m - 1;        else l = m + 1;    }    return -1;}void PushUp(int rt,int l,int r){    if(col[rt]) sum[rt] = p[r + 1] - p[l];    else if(l == r) sum[rt] = 0;    else sum[rt] = sum[rt<<1] + sum[rt<<1 | 1];}void Update(int L,int R,int c,int l,int r,int rt){    if(L <= l && r <= R)    {        col[rt] += c;        PushUp(rt,l,r);        return ;    }    mid;    if(L <= m) Update(L,R,c,lson);    if(m < R) Update(L,R,c,rson);    PushUp(rt,l,r);}int main(){    int n,m;    double x1,x2,y1,y2;    int cas = 1;    while(~sf("%d",&n) && n)    {        m = 0;        for(int i = 0;i < n;i ++)        {            sf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);            p[m] = x1;            ss[m++] = seg(x1,x2,y1,1);            p[m] = x2;            ss[m++] = seg(x1,x2,y2,-1);        }        sort(p,p+m);        sort(ss,ss + m);        int k = 1;        for(int i = 1;i < m;i ++)//去重            if(p[i] != p[i - 1]) p[k++] = p[i];        memset(sum,0,sizeof(sum));        memset(col,0,sizeof(col));        double ans = 0;        for(int i = 0;i < m - 1;i ++)        {            int l = Binary(ss[i].l,0,k - 1);            int r = Binary(ss[i].r,0,k - 1) - 1;//因为每个点代表的是一段距离,因此[l,r]线段长度可以用线段树[l,r-1]区间去存            if(l <= r) Update(l,r,ss[i].f,0,k - 1,1);            ans += (ss[i + 1].h - ss[i].h) * sum[1];        }        printf("Test case #%d\nTotal explored area: %.2lf\n\n",cas++ , ans);    }    return 0;}
0 0