愚人线段树

连续区间更新和询问

POJ 3468
连续交了几次还WA,错误点比较多
代码+解释,盲打就指日可待啦

/*线段树之连续区间更新和询问*/
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
#define N 100001
#define LL long long

LL a[N], tree[3*N], inc[3*N];
void add(int node, int beg, int end, int l, int r, LL d)
{
    if(r < beg || l > end || l > r) return;
                               /*l>r起到剪枝作用*/
    if(l <= beg && end <= r)
    {
        inc[node] += d;
        return;
    }
    tree[node] += (r-l+1)*d;
                       /*被更改区间的父区间值被修改,容易忘*/
    int mid = (beg+end) >> 1;
    add(node*2, beg, mid, l, min(r, mid), d);
    add(node*2+1, mid+1, end, max(mid+1, l), r, d);
                            /*min,max为了简化而设定,可行*/
}
void build(int node, int l, int r)
{
    if(l == r)
        tree[node] = a[l];
    else{
        int mid = (l + r) >> 1;
        build(node*2, l, mid);
        build(node*2+1, mid+1, r);
        tree[node] = tree[node*2] + tree[node*2+1];
    }
}

/*add与query的结构相似*/
LL query(int node, int beg, int end, int l, int r)
{
    if(r < beg || l > end || l > r) return 0;
    if(l <= beg && end <= r)
        return tree[node] + (end-beg+1)*inc[node];
          /*当询问区间被覆盖,则不需继续往下更新,直接返回结果*/
    int mid = (beg+end) >> 1;
    if(inc[node])//剪枝
    {
            /*向下更新子区间,inc是对一个小区间所有数而言的*/
        tree[node] += inc[node]*(end-beg+1);
        add(node*2, beg, mid, beg, mid, inc[node]);
        add(node*2+1, mid+1, end, mid+1, end, inc[node]);
        inc[node] = 0;
    }
    return query(node*2, beg, mid, l, min(mid, r))
        + query(node*2+1, mid+1, end, max(mid+1,l), r);
}

int main()
{
    int n, m;
    while(~scanf("%d%d", &n, &m))
    {
        for(int i = 0;i < n;i++)
            scanf("%I64d", &a[i]);
        memset(inc, 0, sizeof(inc));
        build(1, 0, n-1);
        char str[10];
        while(m--)
        {
            int l, r, d;
            scanf("%s%d%d", str, &l, &r);
            if(str[0] == 'Q')
            {
                LL ans = query(1, 0, n-1, l-1, r-1);
                printf("%I64d\n", ans);
            }
            else
            {
                scanf("%d", &d);
                add(1, 0, n-1, l-1, r-1, d);
            }
        }       
    }
    return 0;
}

扫描线

POJ 1151
这题坑了3天了,终于AC,大概花了半天想清楚了原理,但一直手残敲不出来。输出中间过程反而不太容易分析出来,还是应该定义好每个变量的含义。
cover是一个区间被完全覆盖时,计数覆盖了几重。子区间不用继承父区间的重数,否则在更新时会出错!(这点坑了一天)
has是一个区间完全没被覆盖置为0,否则只要沾边就置为1.当子区间has为1时,父区间has必定为1.同时该区间cover>0时,has必为1.

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
#define N 105
#define eps 1e-8
int n;
double a[N*2], ans, len;
int cover[N*8], has[N*8], all;
struct node
{
    double x1, y1, x2, y2;
    int flag;
}s[N*2];
void build(int rt, int l, int r)
{
    all++;
    cover[rt] = has[rt] = 0;
    if(r != l)
    {
        int mid = l+r >> 1;
        build(rt<<1, l, mid);
        build(rt<<1|1, mid+1, r);
    }
}
int get(double x)
{
    for(int i = 0;i < 2*n;i++)
        if(fabs(x-a[i]) < eps)
            return i;
}
void update(int rt, int beg, int end, int l, int r, int flag)
{
    if(l > r || end < l || r < beg) return;
    if(l <= beg && end <= r)
    {
        cover[rt] += flag;
        if(beg == end)
            has[rt] = cover[rt] ? 1 : 0;
        else
            has[rt] = cover[rt]||has[rt<<1]||has[rt<<1|1] ? 1 : 0;
        return;
    }
    int mid = beg + end >> 1;
    update(rt<<1, beg, mid, l, min(r, mid), flag);
    update(rt<<1|1, mid+1, end, max(mid+1, l), r, flag);
    has[rt] = cover[rt]||has[rt<<1]||has[rt<<1|1] ? 1 : 0;
}
double query(int rt, int l, int r)
{
    if(!has[rt])
        return 0;
    if(cover[rt])
        return a[r+1]-a[l];
    int mid = l+r >> 1;
    return query(rt<<1, l, mid) + 
    query(rt<<1|1, mid+1, r);
}
bool cmp(node p, node q)
{
    return p.y1 < q.y1;
}
void pr()
{
    for(int i = 1;i <= all;i++)
        printf("%d ", i);
    printf("\n");
    for(int i = 1;i <= all;i++)
        printf("%d ", cover[i]);
    printf("\n");
    for(int i = 1;i <= all;i++)
        printf("%d ", has[i]);
    printf("\n");
}
int main()
{
    int o = 0;
    while(scanf("%d", &n), n)
    {
        ans = 0;
        for(int i = 0;i < n;i++)
        {
            int d = 2*i;
            scanf("%lf%lf", &s[d].x1, &s[d].y1);
            scanf("%lf%lf", &s[d+1].x2, &s[d+1].y2);
            s[d].x2 = s[d+1].x2;
            s[d].y2 = s[d].y1;
            s[d+1].x1 = s[d].x1;
            s[d+1].y1 = s[d+1].y2;
            a[d] = s[d].x1;
            a[d+1] = s[d].x2;
            s[d].flag = 1;
            s[d+1].flag = -1;
        }
        all = 0;
        sort(a, a+2*n);
        sort(s, s+2*n, cmp);
        build(1, 0, 2*n-2);
        for(int i = 0;i < 2*n-1;i++)
        {
            int l = get(s[i].x1), r = get(s[i].x2)-1;
            //printf("l= %d r=%d\n", l, r);
            update(1, 0, 2*n-2, l, r, s[i].flag);
            len = query(1, 0, 2*n-2);
            ans += len*(s[i+1].y1-s[i].y1);
            //pr();
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n", ++o, ans);
    }
    return 0;
}

树状数组解决单个节点的更新问题

POJ 3321
乍一眼看是多叉树,先把它化为能用线段树解决的问题,先用dfs遍历一遍。
树状数组的性质:

  1. 定义:C为a的树状数组,k为i在二进制下末尾0的个数
    Ci=ai2k+1+...+ai
  2. lowbit(i)=2k=i&(i(i1))
  3. 求和
    sumk=a1+a2+...+ak=Cn1+Cn2+...Cnm
    nm=kni1=nilowbit(ni)
  4. 如果 ai 更新,则需要更新的是
    Cn1,Cn2,...,Cnm
    n1=ini+1=ni+lowbit(ni)
    (从i开始,二进制最后面连续的0逐渐变多,从叶到顶)
  5. Ci=sumisumilowbit(i)
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
#define N 110000
typedef vector<int> VCI;
vector<VCI> T(N); //vector这样写才能过,否则T
//using vector_type = std::vector<int>; C++11版本
int lef[N], rig[N], n, cnt, a[2*N], C[2*N], L[2*N];
void init()
{
    for(int i = 0;i <= n;i++)
        T[i].clear();
    for(int i = 1;i < n;i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        T[u].push_back(v);
    }
    cnt = 0;
}
void dfs(int x)
{
    lef[x] = ++cnt;
    for(int i = 0;i < T[x].size();i++)
        dfs(T[x][i]);
    rig[x] = ++cnt;
    a[lef[x]] = a[rig[x]] = 1;
}
void set()
{
    for(int x = 1;x <= 2*N;x++)
        L[x] = x&(x^(x-1));
}
void give()
{
    for(int i = 1;i <= 2*n;i++)
        C[i] = L[i];
}
int sum(int x)
{
    int r = 0;
    while(x > 0)
    {
        r += C[x];
        x -= L[x];
    }
    return r;
}
void modify(int x, int y)
{
    int i = x;
    while(i <= 2*n)
    {
        C[i] += - a[x] + y;
        i += L[i];
    }
    a[x] = y;
}
int main()
{
    set();
    while(~scanf("%d", &n))
    {
        init();
        dfs(1);
        give();
        int m;
        scanf("%d", &m);
        while(m--)
        {
            char str[2];
            int x;
            scanf("%s%d", str, &x);
            if(str[0] == 'Q')
                printf("%d\n", (sum(rig[x])-sum(lef[x]-1))/2);
            else{
                modify(rig[x], 1-a[rig[x]]);
                modify(lef[x], 1-a[lef[x]]);
            }
        }
    }
    return 0;
}

二维树状数组

  1. 定义:二维数组a的树状数组C
    Cx,y=ai,j
    xlowbit(x)+1ix
    ylowbit(y)+1jy
  2. 求和:
    sumx,y=Ci,j
    i1=x,i2=i1lowbit(i1),...
    j1=y,j2=j1lowbit(j1),...

    POJ 1195
    比较裸的二维题,改变单个值,用树状数组比较好写。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
#define N 1100
int a[N][N], n, C[N][N], L[N];
void set()
{
    for(int i = 0;i < N;i++)
        L[i] = i&((i-1)^i);
}
void init()
{
    scanf("%d", &n);
    memset(a, 0, sizeof(a));
    memset(C, 0, sizeof(C));
}
void add()
{
    int x, y, z;
    scanf("%d%d%d", &x, &y, &z);
    x++; y++;
    int t = max(0, a[x][y]+z)-a[x][y];
    a[x][y] += z;
    while(x <= n)
    {
        int ty = y;
        while(ty <= n)
        {
            C[x][ty] += t;
            ty += L[ty];
        }
        x += L[x];
    }
}
int sum(int x, int y)
{
    int r = 0;
    while(x > 0)
    {
        int ty = y;
        while(ty > 0)
        {
            r += C[x][ty];
            ty -= L[ty];
        }
        x -= L[x];
    }
    return r;
}
void query()
{
    int x1, y1, x2, y2;
    scanf("%d%d%d%d",&x1, &y1, &x2, &y2);
    x2++; y2++;
    int ans = sum(x2,y2)-sum(x1,y2)-sum(x2,y1)+sum(x1,y1);
    printf("%d\n", ans);
}
int main()
{
    set();
    int command;
    while(~scanf("%d", &command))
    {
        int st = 0;
        switch(command)
        {
            case 0: init(); break;
            case 1: add(); break;
            case 2: query(); break;
            default: st = 1; break;
        }
        if(st)
            break;
    }
    return 0;
}

二维线段树

POJ 2155
树套树,子区间不需继承父区间的标记。在询问时,无需动态向下更新,只需沿路根据标记算出对应的值。
参数较多,一不小心会打错……

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
#define N 1100
int n, tree[3*N][3*N], ans;
void up(int xrt, int yrt, int yb, int ye, int yl, int yr)
{
    if(yr < yb || ye < yl || yl > yr) return;
    if(yl <= yb && ye <= yr)
    {
        tree[xrt][yrt] = !tree[xrt][yrt];
        return;
    }
    int ym = yb+ye >> 1;
    up(xrt, yrt<<1, yb, ym, yl, min(ym, yr));
    up(xrt, yrt<<1|1, ym+1, ye, max(ym+1, yl), yr);
}
void update(int xrt, int xb, int xe, int xl, int xr, int yl, int yr)
{
    if(xr < xb || xe < xl || xl > xr) return;
    if(xl <= xb && xe <= xr)
    {
        up(xrt, 1, 1, n, yl, yr);
        return;
    }
    int xm = xb+xe >> 1;
    update(xrt<<1, xb, xm, xl, min(xr, xm), yl, yr);
    update(xrt<<1|1, xm+1, xe, max(xm+1, xl), xr, yl, yr);
}
void que(int xrt, int yrt, int yb, int ye, int yl, int yr)
{
    if(yr < yb || ye < yl || yl > yr) return;
    ans ^= tree[xrt][yrt]; 
            //容易漏啊!从根到子区间一路上的标记都作用到上面,属于第二维
    if(yl <= yb && ye <= yr) return;
    int ym = yb+ye >> 1;
    que(xrt, yrt<<1, yb, ym, yl, min(ym, yr));
    que(xrt, yrt<<1|1, ym+1, ye, max(ym+1, yl), yr);
}
void query(int xrt, int xb, int xe, int xl, int xr, int yl, int yr)
{
    if(xr < xb || xe < xl || xl > xr) return;
    que(xrt, 1, 1, n, yl, yr);
            //不甚理解,也是通往子区间的标记,属于第一维的
    if(xl <= xb && xe <= xr) return;
    int xm = xb+xe >> 1;
    query(xrt<<1, xb, xm, xl, min(xr, xm), yl, yr);
    query(xrt<<1|1, xm+1, xe, max(xm+1, xl), xr, yl, yr);
}
int pr()
{
    for(int i = 1;i <= 3*n;i++)
        for(int j = 1;j <= 3*n;j++)
            printf("%d%c", tree[i][j], " \n"[j==3*n]);
    printf("\n");
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int m;
        while(~scanf("%d%d", &n, &m))
        {
            memset(tree, 0, sizeof(tree));
            while(m--)
            {
                char str[10];
                scanf("%s", str);
                switch(str[0])
                {
                    case 'C':
                        int x1, y1, x2, y2;
                        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
                        update(1, 1, n, x1, x2, y1, y2);
                        break;
                    case 'Q':
                        int x, y;
                        scanf("%d%d", &x, &y);
                        ans = 0;
                        query(1, 1, n, x, x, y, y);
                        printf("%d\n", ans);
                        break;
                }
                // pr();
            }
            if(T)
                printf("\n");
        }
    }
    return 0;
}

你可能感兴趣的:(愚人线段树)