线段树的各种题目

目录

类型:单点更新、区间查询

题目:敌兵布阵——https://vjudge.net/contest/285952#problem/A

 题目:I hate it——https://vjudge.net/contest/285952#problem/B

题目:Balanced Lineup——https://vjudge.net/contest/285952#problem/E

 类型:区间修改,区间查询

题目:Just a Hook——https://vjudge.net/contest/285952#problem/C

 题目:Color the ball——https://vjudge.net/contest/285952#problem/F

题目:Mayor's posters——http://poj.org/problem?id=2528 

 题目:Can you answer these questions?——https://vjudge.net/contest/290499#problem/H

题目:Assign the task (dfs序)——http://acm.hdu.edu.cn/showproblem.php?pid=3974

 题目:Transformation——http://acm.hdu.edu.cn/showproblem.php?pid=4578

 题目:Vases and Flowers(Half Search)——http://acm.hdu.edu.cn/showproblem.php?pid=4614

类型:线段树合并

题目:Hotel——https://vjudge.net/contest/285952#problem/D

 题目:Tunnel Warfare——http://acm.hdu.edu.cn/showproblem.php?pid=1540

 题目:约会安排——http://acm.hdu.edu.cn/showproblem.php?pid=4553

类型:扫描线

题目:Picture——http://acm.hdu.edu.cn/showproblem.php?pid=1828

题目:Atlantis——http://acm.hdu.edu.cn/showproblem.php?pid=1542

题目:覆盖的面积——http://acm.hdu.edu.cn/showproblem.php?pid=1255



类型:单点更新、区间查询

题目:敌兵布阵——https://vjudge.net/contest/285952#problem/A

题意:

N个初始数据,若干操作。
操作类型:
(1) Add i j,i和j为正整数,表示第i个位置加j(j不超过30) 
(2)Sub i j ,i和j为正整数,表示第i个位置减j(j不超过30); 
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个数的总和; 
(4)End 表示结束,这条命令在每组数据最后出现; 

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson root<<1
#define rson root<<1|1
#define LL long long
using namespace std;
const int maxn = 5e4+5;

int a[maxn<<2], n, t;

void pushup(int root){a[root] = a[lson] + a[rson];}

void build(int root, int l, int r)
{
    if(l==r) scanf("%d", &a[root]);
    else {
        int mid = (l+r)>>1;
        build(lson, l, mid);
        build(rson, mid+1, r);
        pushup(root);
    }
}

int query(int root, int l, int r, int L, int R)
{
    if(L <= l && R >= r) return a[root];
    else {
        int mid = (l + r) >> 1, sum=0;
        if(L <= mid) sum += query(lson, l, mid, L, R);
        if(R > mid) sum += query(rson, mid+1, r, L, R);
        return sum;
    }
}

void update(int root, int l, int r, int index, int x)
{
    if(l == r) {
        a[root] += x;
        return ;
    }
    int mid = (l+r)>>1;
    if(index <= mid) update(lson, l, mid, index, x);
    else update(rson, mid+1, r, index, x);
    pushup(root);
}

int main() {
    int Case=0;
    scanf("%d", &t);
    while(t--){
        memset(a, 0, sizeof(a));
        scanf("%d", &n);
        build(1, 1, n);
        //for(int i=1; i<=n*4; i++) cout << a[i] << endl;
        printf("Case %d:\n", ++Case);
        while(1){
            char ch[8]; int x, y;
            scanf("%s", ch);
            if(ch[0]=='E') break;
            else {
                scanf("%d%d", &x, &y);
                if(ch[0]=='Q'){
                    printf("%d\n", query(1, 1, n, x, y));
                }else if(ch[0]=='A'){
                    update(1, 1, n, x, y);
                }else if(ch[0]=='S'){
                    update(1, 1, n, x, -1*y);
                }
            }

        }
    }
    return 0;
}

 题目:I hate it——https://vjudge.net/contest/285952#problem/B

题意:

N个数,两种操作:单点更新、查找区间最大值

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define lson root<<1
#define rson root<<1|1
#define LL long long
using namespace std;
const int maxn = 2e5+5;

int a[maxn<<2], n, m;

void pushup(int root){a[root] = max(a[lson], a[rson]);}

void build(int root, int l, int r)
{
    if(l==r) scanf("%d", &a[root]);
    else {
        int mid = (l+r)>>1;
        build(lson, l, mid);
        build(rson, mid+1, r);
        pushup(root);
    }
}

int query(int root, int l, int r, int L, int R)
{
    if(L <= l && R >= r) return a[root];
    else {
        int mid = (l + r) >> 1, Max=0;
        if(L <= mid) Max = max(Max, query(lson, l, mid, L, R));
        if(R > mid) Max = max(Max, query(rson, mid+1, r, L, R));
        return Max;
    }
}

void update(int root, int l, int r, int index, int x)
{
    if(l == r) {
        a[root] = x;
        return ;
    }
    int mid = (l+r)>>1;
    if(index <= mid) update(lson, l, mid, index, x);
    else update(rson, mid+1, r, index, x);
    pushup(root);
}

int main() {
    while(~scanf("%d%d", &n, &m)){
        build(1, 1, n);
        for(int i=1; i<=m; i++){
            char ch[3]; int x, y;
            scanf("%s%d%d", ch, &x, &y);
            if(ch[0]=='Q'){
                printf("%d\n", query(1, 1, n, x, y));
            }else {
                update(1, 1, n, x, y);
                //for(int i=1; i<=n*4; i++) cout << a[i] << endl;
            }
        }
    }
    return 0;
}

题目:Balanced Lineup——https://vjudge.net/contest/285952#problem/E

题意:

N个数,M个操作:每次输出区间[x,y]内的最大值和最小值之差。

思路:

同时维护最大值和最小值即可

代码:

#include <iostream>
#include <cstdio>
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1, r
#define INF 1e9
using namespace std;
const int maxn = 5e4+5;

int Max[maxn<<2], Min[maxn<<2], N, M, x, y;

void pushup(int rt)
{
    Max[rt] = max(Max[rt<<1], Max[rt<<1|1]);
    Min[rt] = min(Min[rt<<1], Min[rt<<1|1]);
}
void build(int rt, int l, int r)
{
    if(l == r) {
        scanf("%d", &Max[rt]);
        Min[rt] = Max[rt];
        return ;
    }
    int mid = (l+r)>>1;
    build(lson);
    build(rson);
    pushup(rt);
}
int query_Max(int rt, int l, int r, int L, int R)
{
    if(L<=l && R>=r) return Max[rt];
    int mid = (l+r)>>1, ret=0;
    if(L <= mid) ret = max(ret, query_Max(lson, L, R));
    if(R > mid) ret = max(ret, query_Max(rson, L, R));
    return ret;
}
int query_Min(int rt, int l, int r, int L, int R)
{
    if(L<=l && R>=r) return Min[rt];
    int mid = (l+r)>>1, ret=INF;
    if(L <= mid) ret = min(ret, query_Min(lson, L, R));
    if(R > mid) ret = min(ret, query_Min(rson, L, R));
    return ret;
}
int main()
{
    while(~scanf("%d%d", &N, &M)){
        build(1, 1, N);
        for(int i=1; i<=M; i++){
            scanf("%d%d", &x, &y);
            printf("%d\n", query_Max(1, 1, N, x, y)-query_Min(1, 1, N, x, y));
        }
    }
}

 类型:区间修改,区间查询

题目:Just a Hook——https://vjudge.net/contest/285952#problem/C

题意:

长为N、初始为1的数列,操作:将[x,y]区间内的数改为z,最后输出数列所有数之和。

思路:

区间修改即可,最后答案在a[1]内。

代码:

#include<iostream>
#include<cstdio>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1|1
using namespace std;
const int maxn = 100005;
int Sum[maxn << 2], lazy[maxn << 2];
void pushUp(int rt)
{
    Sum[rt] = Sum[rt << 1]+Sum[rt << 1|1];
}
void pushdown(int rt, int m)
{
    if(lazy[rt]){
        lazy[rt<<1] = lazy[rt];
        lazy[rt<<1|1] = lazy[rt];
        Sum[rt<<1] = lazy[rt]*(m-(m>>1));
        Sum[rt<<1|1] = lazy[rt]*(m>>1);
        lazy[rt] = 0;
    }
}
void build(int l, int r, int rt)
{
    lazy[rt] = 0;
    if(l == r){
        Sum[rt] = 1;
        return ;
    }
    int m = (l + r) >> 1;
    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];
    }
    pushdown(rt, r-l+1);
    int m = (l + r) >> 1;
    int ret = 0;
    if(L <= m) ret += query(L, R, lson);
    if(R > m) ret += query(L, R, rson);
   // pushUp(rt);
    return ret;
}
void update(int L, int R, int x, int l, int r, int rt)
{
    if(L <= l && R >= r){
        Sum[rt] = (r-l+1)*x;
        lazy[rt] = x;
        return ;
    }
    pushdown(rt, r-l+1);
    int m = (l + r) >> 1;
    if(L <= m) update(L, R, x, lson);
    if(R > m) update(L, R, x, rson);
    pushUp(rt);
}
int main()
{
    int N, M, t, Case=0;
    scanf("%d", &t);
    while(t--){
        scanf("%d%d", &N, &M);
        build(1, N, 1);

        while(M--){
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            update(x, y, z, 1, N, 1);
        }
        printf("Case %d: The total value of the hook is %d.\n", ++Case, query(1, N, 1, N, 1));
    }
}

 题目:Color the ball——https://vjudge.net/contest/285952#problem/F

题意:

长度为N初始为0的数列,N个操作,每次给[x,y]范围内的数+1。最后输出整个数列。

思路:

区间修改,单点查询

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson root<<1
#define rson root<<1|1
#define LL long long
using namespace std;
const int maxn = 1e5+5;

int a[maxn<<2], lazy[maxn<<2], n, t, cnt, ans[maxn];

void pushup(int root){a[root] = a[lson] + a[rson];}
void pushdown(int root, int len)
{
    if(lazy[root]){
        lazy[lson] += lazy[root];
        lazy[rson] += lazy[root];
        a[lson] += lazy[root]*(len-(len>>1));
        a[rson] += lazy[root]*(len>>1);
        lazy[root] = 0;
    }
}
void build(int root, int l, int r)
{
    lazy[root] = 0;
    if(l==r) a[root] = 0;
    else {
        int mid = (l+r)>>1;
        build(lson, l, mid);
        build(rson, mid+1, r);
        pushup(root);
    }
}

int query(int root, int l, int r, int L, int R)
{
    if(L <= l && R >= r)
        return a[root];
    pushdown(root, r-l+1);
    int mid = (l + r) >> 1, sum=0;
    if(L <= mid) sum += query(lson, l, mid, L, R);
    if(R > mid) sum += query(rson, mid+1, r, L, R);
    return sum;

}

void update(int root, int l, int r, int L, int R, int x)
{
    if(L <= l && R >= r) {
        a[root] += x*(r-l+1);
        lazy[root] += x;
        return ;
    }
    pushdown(root, r-l+1);
    int mid = (l+r)>>1;
    if(L <= mid) update(lson, l, mid, L, R, x);
    if(R > mid) update(rson, mid+1, r, L, R, x);
    pushup(root);
}
int main() {
    while(~scanf("%d", &n)){
        if(n==0) break;
        cnt = 1;
        //build(1, 1, n);
        memset(a, 0, sizeof(a));
        memset(lazy, 0, sizeof(lazy));
        for(int i=1; i<=n; i++){
            int x, y;
            scanf("%d%d", &x, &y);
            update(1, 1, n, x, y, 1);
        }
        for(int i=1; i<=n; i++){
            printf("%d%c", query(1, 1, n, i, i), i==n?'\n':' ');
        }
    }
}

题目:Mayor's posters——http://poj.org/problem?id=2528 

题意:

在一段墙上贴海报,后贴的可以覆盖先贴的,问最后能看到多少种海报。

思路:

题目中 l、r 的最大值在1e7内,若直接建树对其操作空间会爆。好在 n 的范围在1e4之内,而且从题意可知,最后的结果和数据的大小关系不大,重要的是各数据间的相对大小,所以要对数据进行离散化。从后往前贴,若要贴的部分已经有海报了,就忽略。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define LL long long
#define lson root << 1
#define rson root << 1|1
using namespace std;
const int maxn = 1e5+5;
struct Node
{
    int val, index;
    bool flag;
    bool operator < (const Node& a)const{
        if(val == a.val) return index < a.index;
        return val < a.val;
    }
}node[maxn];
struct Ques
{
    int x, y;
}Q[maxn];

int a[maxn<<2], lazy[maxn<<2], ans;
void pushup(int root){a[root] = a[lson] + a[rson];}
void pushdown(int root, int len)
{
    if(lazy[root]){
        lazy[lson] = lazy[root];
        lazy[rson] = lazy[root];
        a[lson] = lazy[root]*(len-(len>>1));
        a[rson] = lazy[root]*(len>>1);
        lazy[root] = 0;
    }
}
void build(int root, int l, int r)
{
    lazy[root] = 0;
    if(l==r) a[root] = 0;
    else {
        int mid = (l+r)>>1;
        build(lson, l, mid);
        build(rson, mid+1, r);
        pushup(root);
    }
}

int query(int root, int l, int r, int L, int R)
{
    if(L <= l && R >= r)
        return a[root];
    pushdown(root, r-l+1);
    int mid = (l + r) >> 1, sum=0;
    if(L <= mid) sum += query(lson, l, mid, L, R);
    if(R > mid) sum += query(rson, mid+1, r, L, R);
    return sum;

}

void update(int root, int l, int r, int L, int R)
{
    if(a[root] == r-l+1) return ;    

    if(L <= l && R >= r) {
        a[root] = (r-l+1);
        lazy[root] = 1;
        return ;
    }
    pushdown(root, r-l+1);
    int mid = (l+r)>>1;
    if(L <= mid) update(lson, l, mid, L, R);
    if(R > mid) update(rson, mid+1, r, L, R);
    pushup(root);
}
int main()
{
    int N, t;
    scanf("%d", &t);
    while(t--){
        scanf("%d", &N);
        int x, y, cnt=0;
        for(int i=1; i<=N; i++){
            scanf("%d%d", &x, &y);
            node[++cnt].flag = 0; node[cnt].val = x; node[cnt].index = i;
            node[++cnt].flag = 1; node[cnt].val = y; node[cnt].index = i;
        }

        sort(node+1, node+cnt+1);        //离散化
        int tmp = 1, last=node[1].val;
        for(int i=1; i<=cnt; i++){
            if(node[i].val > last){
                tmp ++;
            }
            if(node[i].flag == 0) Q[node[i].index].x = tmp;
            else Q[node[i].index].y = tmp;
            last=node[i].val;
        }

        build(1, 1, maxn);
        ans = 0;
        for(int i=N; i>=1; i--){
            if(query(1, 1, maxn, Q[i].x, Q[i].y) == (Q[i].y-Q[i].x+1)) continue;
            ans ++;
            update(1, 1, maxn, Q[i].x, Q[i].y);
        }
        cout << ans << endl;
    }
}

 题目:Can you answer these questions?——https://vjudge.net/contest/290499#problem/H

题意:

对长度为n的数列进行两种操作:
(1)对 [l,r] 内的数据进行开方
(2)询问 [l.r] 范围内的和

思路:

由于一个很大的数进行不了几次开平方操作就会变为1,所以直接进行修改就行,判断区间内是否全为1,如果是就不用再继续进行开方操作了。坑点:数据用long long,题目中是 between x and y .

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#define LL long long
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1|1
using namespace std;
const int maxn = 100005;

LL Sum[maxn << 2], ans[maxn];
void pushUp(int rt)
{
    Sum[rt] = Sum[rt << 1]+Sum[rt << 1|1];
}
void build(int l, int r, int rt)
{
    if(l == r){
        scanf("%lld", &Sum[rt]);
        return ;
    }
    int m = (l + r) >> 1;
    build(lson);
    build(rson);
    pushUp(rt);
}
LL query(int L, int R, int l, int r, int rt)
{
    if(L <= l && R >= r){
        return Sum[rt];
    }
    int m = (l + r) >> 1;
    LL ret = 0;
    if(L <= m) ret += query(L, R, lson);
    if(R > m) ret += query(L, R, rson);
    return ret;
}
void update(int L, int R, int l, int r, int rt)
{
    if(Sum[rt] == (LL)(r-l+1)) return ;    //当范围内全为1时不用操作

    if(l == r && L <= l && R >= r){
        Sum[rt] = (LL)sqrt(Sum[rt]);
        return ;
    }
    int m = (l+r) >> 1;
    if(L <= m) update(L, R, lson);
    if(R > m) update(L, R, rson);
    pushUp(rt);
}
int main()
{
    int N, M, t, Case=0;
    while(scanf("%d", &N) != EOF){
        int cnt = 0;
        build(1, N, 1);
        scanf("%d", &M);
        while(M--){
            int x, y, op;
            scanf("%d%d%d", &op, &x, &y);
            if(x == 0)
                update(min(y, x), max(y, x), 1, N, 1);  //between x and y
            else if(x == 1)
                ans[++cnt] = query(min(y,x), max(y,x), 1, N, 1);    
            //for(int i=1; i<=N; i++){cout << query(i, i, 1, N, 1) << ' ';} cout << endl;
        }
        printf("Case #%d:\n", ++Case);
        for(int i=1; i<=cnt; i++){
            printf("%lld\n", ans[i]);
        }
        cout << endl;
    }

}

题目:Assign the task (dfs序)——http://acm.hdu.edu.cn/showproblem.php?pid=3974

思路:

dfs序+区间修改+单点查询。刚开始打了 init()竟然忘了调用 —_—!(无语……)

代码:

#include <iostream>
#include <cstdio>
#include <stack>
#include <vector>
#include <cstring>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 5e4+5;
int t, n, m, in[maxn], out[maxn], a[maxn<<3], cnt; bool vis_son[maxn];
vector<int>vec[maxn];
void init()
{
    cnt = 0;
    memset(vis_son, false, sizeof(vis_son));
    for(int i=0; i<maxn; i++) vec[i].clear();
}
void dfs(int rt)
{
    in[rt] = ++cnt;
    for(int i=0; i<vec[rt].size(); i++){
        dfs(vec[rt][i]);
    }
    out[rt] = ++cnt;
}
void pushdown(int rt)
{
    if(a[rt] != -1){
        a[lson] = a[rson] = a[rt];
        a[rt] = -1;
    }
}
void build(int rt, int l, int r)
{
    a[rt] = -1;
    if(l==r) return ;
    int mid = (l+r)>>1;
    build(lson, l, mid);
    build(rson, mid+1, r);
}
void update(int rt, int l, int r, int L, int R, int x)
{
    if(L <= l && R >= r){
        a[rt] = x;
        return ;
    }
    pushdown(rt);
    int mid = (l+r) >> 1;
    if(L <= mid) update(lson, l, mid, L, R, x);
    if(R > mid) update(rson, mid+1, r, L, R, x);
}
int query(int rt, int l, int r, int x)
{
    if(l==r) return a[rt];
    pushdown(rt);
    int mid = (l+r)>>1;
    if(x <= mid) query(lson, l, mid, x);
    else query(rson, mid+1, r, x);
}
int main()
{
    int Case=0;
    cin >>t ;
    while(t--){
        cin >> n;
        init();   
        for(int i=1; i<=n-1; i++){    //储存树
            int son , fa;
            cin >> son >> fa;
            vec[fa].push_back(son);
            vis_son[son] = true;
        }
        int fa;
        for(int i=1; i<=n; i++)    //找大boss
            if(!vis_son[i]){
                fa = i; break;
            }
        dfs(fa);
        build(1, 1, cnt);
        char ch[2]; int x, y;
        cin >> m;
        printf("Case #%d:\n", ++Case);
        for(int i=1; i<=m; i++){
            scanf("%s", ch);
            if(ch[0] == 'C'){
                cin >> x;
                cout << query(1, 1, cnt, in[x]) << endl;
            }else {
                cin >> x >> y;
                update(1, 1, cnt, in[x], out[x], y);
            }
        }
    }
}

 题目:Transformation——http://acm.hdu.edu.cn/showproblem.php?pid=4578

题意:

四种操作:1、[l,r]内各数+c
                  2、[l,r]内各数*c
                  3、[l,r]内各数变为c
                  4、[l,r]内各数的p次方之和

思路:

网上有很多直接刚的代码,可怕之极,找到了一种简单思路,但应该不是什么正经解法,可能是这题的数据比较友好。由于是对范围进行操作,所以如果数据不是太刁钻的话会存在很多区间内的数都相等的情况。用flag[]数组代表区间内的数是否相等,a[]数组中保存范围内的数。

代码:

#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int mod = 10007;
const int maxn=1e5+5;

int a[maxn<<2];
bool flag[maxn<<2];
void pushup(int rt)
{
    if(!flag[lson] || !flag[rson] || (a[lson] != a[rson])) flag[rt] = false;
    else flag[rt] = true, a[rt] = a[lson] = a[rson];
}
void pushdown(int rt)
{
    if(flag[rt]){
        flag[lson] = flag[rson] = flag[rt];
        a[lson] = a[rson] = a[rt];
        flag[rt] = false;
    }
}
void update(int rt, int l, int r, int L, int R, int x, int op)
{
    if(L <= l && R >= r && flag[rt]) {
        if(op == 1) a[rt] = (a[rt]+x)%mod;
        else if(op == 2) a[rt] = (a[rt]*x)%mod;
        else a[rt] = x;
        return ;
    }
    pushdown(rt);
    int mid = (l+r)>>1;
    if(L <= mid) update(lson, l, mid, L, R, x, op);
    if(R > mid) update(rson, mid+1, r, L, R, x, op);
    pushup(rt);
}
int query(int rt, int l, int r, int L, int R, int k)
{
    if(L <= l && R >= r && flag[rt]) {
        int ans = 1;
        for(int i=1; i<=k; i++) ans = (a[rt]*ans)%mod;
        return (ans*(r-l+1))%mod;
    }
    pushdown(rt);
    int mid = (l+r) >> 1, ans = 0;
    if(L <= mid) ans = (ans+query(lson, l, mid, L, R, k))%mod;
    if(R > mid) ans = (ans+query(rson, mid+1, r, L, R, k))%mod;
    return ans%mod;
}
int main()
{
    int n,m;
    while(scanf("%d%d", &n, &m) && (n||m)){
        fill(a, a+(maxn<<2), 0);
        fill(flag, flag+(maxn<<2), true);
        int op, x, y, z;
        while(m--){
            scanf("%d%d%d%d",&op,&x,&y,&z);
            if(op>=1 && op<=3) update(1, 1, n, x, y, z, op);
            else printf("%d\n", query(1, 1, n, x, y, z));
        }
    }
}

 题目:Vases and Flowers(Half Search)——http://acm.hdu.edu.cn/showproblem.php?pid=4614

题意:

n个空花瓶,两种操作:
1、每次从 x 位置向后插 y 个花,若已占用就继续向后找空的,直到花被用完或瓶被插完。
2、清空 [x,y] 内的花瓶。

思路:

        在1操作中,如果x位置已定,很明显可以用二分处右边位置保证满足花被用完或瓶被插完。刚开始想的二分有点复杂,虽然x的位置已经确定,但可能已经被占用,输出结果要求是开始插的起始位置和结束位置。所以先找到x右边的第一个空的花瓶位置x0,作为起始位置,然后找从 x0 开始插 y 个花的末尾点,若空位置<y,返回最后一个空位置即可。二分和查找都是log(n),m次操作O(m*log(n)*log(n))。额……思路很生硬,用了三个二分。后来看了别人的代码,只用一个二分即可,在x开始插y个,起始位置其实就是y=1的情况,结束位置是 y=min(y, x后所有的空位置),做的题少果然思路受限。
       除了二分之外,因为我刚开始自作聪明导致时间超限。因为数组中只有0和1两种状态,其实只下推延迟标记也能记录正确的变化,但查询时只有区间内全是0或1时可以直接返回r-l+1,否则就要向下查找。如果串是0101010……每次都要叶推到子节点,相当于遍历了整棵树,效率极低。本来还想一个数组就能维护何必用两个呢,结果却是聪明反被聪明误……

代码:

一个数组维护的超时代码,虽然写了三个二分,但好在思路是对的,超时原因是因为线段树维护时的问题,只把线段树改成正常两个数组维护的就行了。聪明反被聪明误,贴以警示……

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson rt<<1
#define rson rt<<1|1
#define LL long long
using namespace std;
const int maxn = 5e4+5;

int t, n, m, x, y, op;
int lazy[maxn<<2];
void pushdown(int rt)
{
    if(lazy[rt]!=-1){
        lazy[lson] = lazy[rson] = lazy[rt];
        lazy[rt] = -1;
    }
}
void update(int rt, int l, int r, int L, int R, int op)
{
    if(L <= l && R >= r) {
        lazy[rt] = (op==1);
        return ;
    }
    pushdown(rt);
    int mid = (l+r)>>1;
    if(L <= mid) update(lson, l, mid, L, R, op);
    if(R > mid) update(rson, mid+1, r, L, R, op);
}
int query(int rt, int l, int r, int L, int R)
{
    if(l == r) return (lazy[rt]==-1) ? 0 : lazy[rt];
    if(L <= l && R >= r && lazy[rt]!=-1) {            //这里要范围内全是0或1时才能返回
        return lazy[rt]*(r-l+1);
    }
    pushdown(rt);
    int mid = (l+r)>>1, ans = 0;
    if(L <= mid) ans += query(lson, l, mid, L, R);
    if(R > mid) ans += query(rson, mid+1, r, L, R);
    return ans;
}
int erfen1(int x)   //在 index=x 右边的第一个0 的下标
{
    int l=x, r=n;
    while(l<=r){
        int mid = (l+r)>>1;
        if(query(1, 1, n, x, mid) == mid-x+1) l=mid+1;
        else r=mid-1;
    }
    return l>n ? -1 : l;
}
int erfen3()   //最右边的一个0的下标
{
    int l=0, r=n;
    while(l<=r){
        int mid=(l+r)>>1;
        if(query(1, 1, n, mid, n) == n-mid+1) r=mid-1;
        else l=mid+1;
    }
    return r;
}
int erfen2(int x, int y)    //刚好填满y个的最右端下标
{
    int l=x, r=n;
    if(r-l+1-query(1, 1, n, l, r) < y) {    // x 后的位置全填满
        int t = erfen3();   //最后填的位置
        if(t < x) return -1;
        return t;
    }
    while(l<=r){
        int mid=(l+r)>>1;
        if(mid-x+1-query(1, 1, n, x, mid) < y) l=mid+1;
        else r=mid-1;
    }
    return l;
}
int main(){
    cin >> t;
    while(t--){
        cin >> n >> m;
        memset(lazy, -1, sizeof(lazy));
        for(int i=1; i<=m; i++){
            cin >> op >> x >> y;
            x++, y++;
            if(op == 1){
                y--;
                int t = erfen2(x, y);
                if(t==-1) cout << "Can not put any one." << endl;
                else cout << erfen1(x)-1 << " " << t-1 << endl;
                update(1, 1, n, x, t, op);
            }else {
                cout << query(1, 1, n, x, y) << endl;
                update(1, 1, n, x, y, op);
            }
        }
    }
}

然后是一个二分的,刚开始build最后忘了pushup(可以直接用memset清空)结果导致第一组的数据对第二组有影响,又盯了好长时间,真是一步一个坑啊。。。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson rt<<1
#define rson rt<<1|1
#define LL long long
using namespace std;
const int maxn = 5e4+5;

int t, n, m, x, y, op;
int lazy[maxn<<2], sum[maxn<<2];
void pushup(int rt)
{
    sum[rt] = sum[lson] + sum[rson];
}
void pushdown(int rt, int len)
{
    if(lazy[rt]!=-1){
        lazy[lson] = lazy[rson] = lazy[rt];
        sum[lson] = lazy[rt]*(len-(len>>1));
        sum[rson] = lazy[rt]*(len>>1);
        lazy[rt] = -1;
    }
}
void build(int rt, int l, int r)
{
    lazy[rt] = -1;
    if(l==r) {
        sum[rt] = 0;
        return ;
    }
    int mid=(l+r)>>1;
    build(lson, l, mid);
    build(rson, mid+1, r);
    pushup(rt);
}
void update(int rt, int l, int r, int L, int R, int x)
{
    if(L <= l && R >= r) {
        sum[rt]=x*(r-l+1);
        lazy[rt]=x;
        return ;
    }
    pushdown(rt, r-l+1);
    int mid = (l+r)>>1;
    if(L <= mid) update(lson, l, mid, L, R, x);
    if(R > mid) update(rson, mid+1, r, L, R, x);
    pushup(rt);
}
int query(int rt, int l, int r, int L, int R)
{
    if(L <= l && R >= r) return sum[rt];
    pushdown(rt, r-l+1);
    int mid = (l+r)>>1, ans = 0;
    if(L <= mid) ans += query(lson, l, mid, L, R);
    if(R > mid) ans += query(rson, mid+1, r, L, R);
    return ans;
}
int erfen(int x, int y)
{
    int num = query(1, 1, n, x, n);
    if(num == n-x+1) return -1;    // x 后面没有空位置
    if(n-x+1-num < y) y=n-x+1-num;

    int l=x, r=n, index=-1;
    while(l<=r){
        int mid=(l+r)>>1, temp = mid-x+1-query(1, 1, n, x, mid);
        if(temp < y) l=mid+1;
        else if(temp > y) r=mid-1;
        else index=mid, r=mid-1;
    }
    return index;
}
int main(){
    scanf("%d", &t);
    while(t--){
        scanf("%d%d", &n, &m);
        build(1, 1, n);
        for(int i=1; i<=m; i++){
            scanf("%d%d%d", &op, &x, &y);
            x++, y++;    //代码是1~n,题目是0~n-1
            if(op == 1){
                y--;
                int l = erfen(x, 1);
                if(l==-1) printf("Can not put any one.\n");
                else {
                    int r = erfen(x, y);
                    printf("%d %d\n", l-1, r-1);
                    update(1, 1, n, l, r, 1);
                }
                update(1, 1, n, x, y, 1);
            }else {
                printf("%d\n", query(1, 1, n, x, y));
                update(1, 1, n, x, y, 0);
            }
        }
        printf("\n");
    }
}

 题目:Party(线段树+二分)——http://acm.hdu.edu.cn/showproblem.php?pid=6521

题意:

起初所有人互不认识,问每次让[l , r]内的人参加聚会之前[l, r]中互不认识的人的对数

思路:

本题最主要的一点是要把区间化成点。当给出更新 [l, r] 后,表示 [l, r] 内的人都相互认识,可以用 a[i] = j 表示从 i 到 j  的人都相互认识,初始 a[i] = i。可以发现任意时刻的a[]数组都是非减的。
更新时,将 [l, r]中小于 r 的数全更新为 r,由于数组是有序的,可以二分出第一个小于 r 的位置。
查询时,对于a[l..r] 中 的 a[i] >= r,对答案不产生贡献,对于a[i] < r 的,产生 r - a[i] 的贡献,同样而分出第一个小于 r 的位置。

也可用吉司机线段树——https://blog.youkuaiyun.com/LSD20164388/article/details/89413657,思想和上述相同,实现更简单,但每次区间更新时要跑到叶子节点。

对于下列样例:

6 3---1 2 3 4 5 6 初始. n = 6, m = 3
3 5---1 2 5 5 5 6
2 4---1 4 5 5 5 6
5 6---1 4 5 5 6 6

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define lson rt<<1
#define rson rt<<1|1
#define LL long long
using namespace std;
const int maxn=5e5+5;

LL n, m, x, y, tr[maxn<<2], maxx[maxn<<2], sum[maxn<<2], lazy[maxn<<2];
void pushup(int rt){
    sum[rt] = sum[lson] + sum[rson];
    maxx[rt] = max(maxx[lson], maxx[rson]);
}
void pushdown(int rt, int len){
    if(lazy[rt]) {
        lazy[lson] = max(lazy[lson], lazy[rt]);
        lazy[rson] = max(lazy[rson], lazy[rt]);
        maxx[lson] = max(maxx[lson], maxx[rt]);
        maxx[rson] = max(maxx[rson], maxx[rt]);
        sum[lson] = maxx[lson] * (len-(len>>1));
        sum[rson] = maxx[rson] * (len>>1);
        lazy[rt] = 0;
    }
}
void build(int rt, int l, int r){
    lazy[rt] = 0;
    if(l == r) {
        sum[rt] = maxx[rt] = l;
        return ;
    }
    int mid = l + r >> 1;
    build(lson, l, mid);
    build(rson, mid+1, r);
    pushup(rt);
}
int getpos(int rt, int l, int r, int p){
    pushdown(rt, r-l+1);
    if(l == r) return maxx[rt];
    int mid = l+r >> 1;
    if(p <= mid) return getpos(lson, l, mid, p);
    else return getpos(rson, mid+1, r, p);
}
void update(int rt, int l, int r, int L, int R, LL val){
    if(L <= l && R >= r) {
        maxx[rt] = max(maxx[rt], val);
        sum[rt] = 1LL*(r-l+1)*maxx[rt];
        lazy[rt] = max(lazy[rt], val);
        return ;
    }
    pushdown(rt, r-l+1);
    int mid = l+r >> 1;
    if(L <= mid) update(lson, l, mid, L, R, val);
    if(R > mid) update(rson, mid+1, r, L, R, val);
    pushup(rt);
}
LL query(int rt, int l, int r, int L, int R){
    if(L <= l && R >= r) return sum[rt];
    pushdown(rt, r-l+1);
    int mid = l+r >> 1; LL ret = 0;
    if(L <= mid) ret += query(lson, l, mid, L, R);
    if(R > mid) ret += query(rson, mid+1, r, L, R);
    pushup(rt);
    return ret;
}
int main()
{
    while(~scanf("%lld%lld", &n, &m)){
        build(1, 1, n);
        for(int i=1; i<=m; i++){
            scanf("%lld%lld", &x, &y);
            LL l=1, r=n, p=0;
            while(l <= r){
                int mid = l+r >> 1;
                if(getpos(1, 1, n, mid) < y)
                    p = mid, l = mid+1;
                else r = mid - 1;
            }
            p = min(p, y);
            if(x <= p){
                printf("%lld\n", 1LL * (p - x + 1)*y - query(1, 1, n, x, p));
                update(1, 1, n, x, p, y);
            }else printf("0\n");
        }
    }
}

类型:线段树合并

题目:Hotel——https://vjudge.net/contest/285952#problem/D

题意:

N个房间1到N,M个操作。操作分两种,第一种是查询是否有足够的连续房间,若存在则返回最左边房间编号,并占用房间,否则返回0;第二种操作是清楚以x为起点长度为y的房间。

思路:

线段树的合并。最长连续区间长度问题,L[root]表示区间内从左到右的最长连续序列(本题是最长连续空房间)的长度,R[root]表示区间内从右到左的最长连续序列(最长连续空房间)的长度,a[root]表示区间内最长连续序列(最长连续空房间)的长度。更新时用到lazy标记,lazy=1表示空房间,lazy=0表示房间被占用,lazy=-1表示未被标记。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson root<<1
#define rson root<<1|1
#define LL long long
using namespace std;
const int maxn = 5e4+5;

int a[maxn<<2], lazy[maxn<<2], L[maxn<<2], R[maxn<<2];

void pushup(int root, int l, int r)
{
    L[root] = L[lson];
    R[root] = R[rson];
    a[root] = max(R[lson] + L[rson], max(a[lson], a[rson]));
    int mid = (l+r)>>1;
    if(a[lson] == mid-l+1) L[root] += L[rson];
    if(a[rson] == r-(mid+1)+1) R[root] += R[lson];
}
void pushdown(int root, int l, int r)
{
    if(lazy[root]!=-1){
        lazy[lson] = lazy[rson] = lazy[root];
        int mid = (l+r)>>1;
        L[lson] = R[lson] = a[lson] = lazy[root]*(mid-l+1);
        L[rson] = R[rson] = a[rson] = lazy[root]*(r-(mid+1)+1);
        lazy[root] = -1;
    }
}
void build(int root, int l, int r)
{
    L[root] = R[root] = a[root] = (r-l+1);
    lazy[root] = 1;
    if(l==r) return ;

    int mid = (l+r)>>1;
    build(lson, l, mid);
    build(rson, mid+1, r);
}

int query(int root, int l, int r, int len)
{
    if(L[root] >= len) return l;
    int mid = (l + r) >> 1;
    if(a[lson] >= len) query(lson, l, mid, len);
    else if(R[lson] + L[rson] >= len) return mid-R[lson]+1;
    else query(rson, mid+1, r, len);
}

void update(int root, int l, int r, int L_, int R_, bool isclear)
{
    if(L_ <= l && R_ >= r) {
        if(isclear){
            L[root] = R[root] = a[root] = (r-l+1);
            lazy[root] = 1;
        }else {
            L[root] = R[root] = a[root] = 0;
            lazy[root] = 0;
        }
        return ;
    }
    pushdown(root, l, r);
    int mid = (l+r)>>1;
    if(L_ <= mid) update(lson, l, mid, L_, R_, isclear);
    if(R_ > mid) update(rson, mid+1, r, L_, R_, isclear);
    pushup(root, l, r);
}
int main() {
    int n, m, x, y, op;
    scanf("%d%d", &n, &m);
    build(1, 1, n);
    for(int i=1; i<=m; i++){
        scanf("%d", &op);
        if(op==1) {
            scanf("%d", &x);
            if(a[1] < x) {printf("0\n"); continue;};
            int ans=query(1, 1, n, x);
            printf("%d\n", ans);
            update(1, 1, n, ans, ans+x-1, 0);
        }else {
            scanf("%d%d", &x, &y);
            update(1, 1, n, x, x+y-1, 1);
        }
    }
}

 题目:Tunnel Warfare——http://acm.hdu.edu.cn/showproblem.php?pid=1540

题意:

n个点,每次摧毁一个或恢复上一个被摧毁的点,询问与一个点直接和间接相邻的点的个数(包括自身)。

思路:

单点修改,区间合并。对模板的修改主要在 query() 函数。询问位置x时,若x在node[rt].lm区域内,直接返回node[rt].lm,右侧同理,否则就处于中间,再进入左子树或右子树判断,若此时x在node[lson].rm区域内,就和node[rson].lm区域连上了,要返回两者之和;右子树的判断同理。判断 x 的位置时要注意。
题目有问题,数据是多组输入。注意每个村庄摧毁多次也要压栈。

代码:

#include <iostream>
#include <cstdio>
#include <stack>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 1e5+5;

struct Node
{
    int lm, rm, mm;
}node[maxn << 2];
int n, m;
void pushup(int rt, int l, int r)
{
    node[rt].lm = node[lson].lm;
    node[rt].rm = node[rson].rm;
    node[rt].mm = max(node[lson].rm+node[rson].lm, max(node[lson].mm, node[rson].mm));
    int mid = (l+r)>>1;
    if(node[rt].lm==mid-l+1) node[rt].lm += node[rson].lm;
    if(node[rt].rm==r-(mid+1)+1) node[rt].rm += node[lson].rm;
}
void build(int rt, int l, int r)
{
    node[rt].lm = node[rt].rm = node[rt].mm = r-l+1;
    if(l == r) return ;
    int mid = (l+r) >> 1;
    build(lson, l, mid);
    build(rson, mid+1, r);
}
void update(int rt, int l, int r, int x, bool flag)
{
    if(l == r) {
        if(flag) node[rt].lm = node[rt].rm = node[rt].mm = 1;
        else node[rt].lm = node[rt].rm = node[rt].mm = 0;
        return ;
    }
    int mid = (l+r) >> 1;
    if(x <= mid) update(lson, l, mid, x, flag);
    else update(rson, mid+1, r, x, flag);
    pushup(rt, l, r);
}
int query(int rt, int l, int r, int x)
{
    if(l==r) return node[rt].mm;

    if(x <= node[rt].lm+l-1) return node[rt].lm;
    if(x >= r-node[rt].rm+1) return node[rt].rm;

    int mid = (l+r)>>1;
    if(x <= mid){
        if(x >= mid-node[lson].rm+1) return node[lson].rm+node[rson].lm;
        return query(lson, l, mid, x);
    }else {
        if(x <= (mid+1)+node[rson].lm-1) return node[lson].rm+node[rson].lm; //注意是mid+1
        return query(rson, mid+1, r, x);
    }
}
stack<int>sta;
int main()
{
    while(cin >> n >> m){
        while(!sta.empty()) sta.pop();
        build(1, 1, n);
        char ch[2]; int x;
        for(int i=1; i<=m; i++){
            scanf("%s", ch);
            if(ch[0] == 'D'){
                scanf("%d", &x);
                sta.push(x);       //无论是否已被摧毁都要压栈
                update(1, 1, n, x, false);
            }else if(ch[0] == 'Q'){
                scanf("%d", &x);
                cout << query(1, 1, n, x) << endl;
            }else {
                if(!sta.empty()){
                    update(1, 1, n, sta.top(), true);
                    sta.pop();
                }
            }
        }
    }
}

 题目:约会安排——http://acm.hdu.edu.cn/showproblem.php?pid=4553

题意:

为两个人寻找连续的 y 个空位,且两人的优先级不同。

思路:

和Hotel——https://vjudge.net/contest/285952#problem/D差不多,相当于有两个人且优先级不同,建两棵树即可,一棵DS树,一棵NS树,当DS时,在DS树中正常查找;当NS时,先在DS树中找,没找到再去NS树中找,更新时要同时更新两棵。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson root<<1
#define rson root<<1|1
#define LL long long
using namespace std;
const int maxn = 1e5+5;

int a[maxn<<2][2], lazy[maxn<<2][2], L[maxn<<2][2], R[maxn<<2][2];

void pushup(int root, int l, int r, int flag)
{
    L[root][flag] = L[lson][flag];
    R[root][flag] = R[rson][flag];
    a[root][flag] = max(R[lson][flag] + L[rson][flag], max(a[lson][flag], a[rson][flag]));
    int mid = (l+r)>>1;
    if(a[lson][flag] == mid-l+1) L[root][flag] += L[rson][flag];
    if(a[rson][flag] == r-(mid+1)+1) R[root][flag] += R[lson][flag];
}
void pushdown(int root, int l, int r, int flag)
{
    if(lazy[root][flag]!=-1){
        lazy[lson][flag] = lazy[rson][flag] = lazy[root][flag];
        int mid = (l+r)>>1;
        L[lson][flag] = R[lson][flag] = a[lson][flag] = lazy[root][flag]*(mid-l+1);
        L[rson][flag] = R[rson][flag] = a[rson][flag] = lazy[root][flag]*(r-(mid+1)+1);
        lazy[root][flag] = -1;
    }
}
void build(int root, int l, int r, int flag)
{
    L[root][flag] = R[root][flag] = a[root][flag] = (r-l+1);
    lazy[root][flag] = 1;
    if(l==r) return ;

    int mid = (l+r)>>1;
    build(lson, l, mid, flag);
    build(rson, mid+1, r, flag);
}

int query(int root, int l, int r, int len, int flag)
{
    if(L[root][flag] >= len) return l;
    int mid = (l + r) >> 1;
    if(a[lson][flag] >= len) query(lson, l, mid, len, flag);
    else if(R[lson][flag] + L[rson][flag] >= len) return mid-R[lson][flag]+1;
    else query(rson, mid+1, r, len, flag);
}

void update(int root, int l, int r, int L_, int R_, bool isclear, int flag)
{
    if(L_ <= l && R_ >= r) {
        if(isclear){
            L[root][flag] = R[root][flag] = a[root][flag] = (r-l+1);
            lazy[root][flag] = 1;
        }else {
            L[root][flag] = R[root][flag] = a[root][flag] = 0;
            lazy[root][flag] = 0;
        }
        return ;
    }
    pushdown(root, l, r, flag);
    int mid = (l+r)>>1;
    if(L_ <= mid) update(lson, l, mid, L_, R_, isclear, flag);
    if(R_ > mid) update(rson, mid+1, r, L_, R_, isclear, flag);
    pushup(root, l, r, flag);
}
int main() {
    int t, n, m, Case=0;
    scanf("%d", &t);
    while(t--){
        scanf("%d%d", &n, &m);
        build(1, 1, n, 0); build(1, 1, n, 1);
        int x, y; char op[10];
        printf("Case %d:\n", ++Case);
        for(int i=1; i<=m; i++){
            scanf("%s", op);
            if(op[0] == 'D'){
                scanf("%d", &x);
                if(a[1][0] < x) {printf("fly with yourself\n"); continue;}
                int l = query(1, 1, n, x, 0);
                printf("%d,let's fly\n", l);
                update(1, 1, n, l, l+x-1, 0, 0);
            }else if(op[0] == 'N'){
                scanf("%d", &x);
                if(a[1][1]<x) {printf("wait for me\n"); continue;}
                int l = query(1, 1, n, x, (a[1][0]<x));
                printf("%d,don't put my gezi\n", l);
                update(1, 1, n, l, l+x-1, 0, 1);
                update(1, 1, n, l, l+x-1, 0, 0);
            }else {
                scanf("%d%d", &x, &y);
                printf("I am the hope of chinese chengxuyuan!!\n");
                update(1, 1, n, x, y, 1, 0);
                update(1, 1, n, x, y, 1, 1);
            }
        }
    }
}

类型:扫描线

题目:Picture——http://acm.hdu.edu.cn/showproblem.php?pid=1828

题意:

给出n个矩形的左下和右上点的坐标,求覆盖图形的周长

思路:

扫描线。第一种比较好理解,可以保存纵边和横边,上下扫一下再左右扫一下求和。第二种可以自己画图思考以下,可以只保存纵边或横边,维护区间内的不相交线段个数num,每次ans+=tree[1].num*2*(edge[i+1].h-edge[i].h)。

代码:

扫两次的:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 1e5+5;

struct Edge
{
    int l, r, h, f;
    bool operator < (const Edge& e) const {
        return h==e.h ? f>e.f : h<e.h;  //先排入边可以防止上边与下边重合时出错
    }
}edge[2][maxn]; //0为横边,1为纵边
struct Tree
{
    int sum, len;
}tree[maxn<<2];

int n, num_edge;

void addedge(int l, int r, int h, int f, int flag)
{
    if(!flag) ++num_edge;
    edge[flag][num_edge].l = l, edge[flag][num_edge].r = r;
    edge[flag][num_edge].h = h; edge[flag][num_edge].f = f;
}

void pushup(int rt, int l, int r)
{
    if(tree[rt].sum) tree[rt].len = r-l+1;
    else if(l == r) tree[rt].len = 0;
    else tree[rt].len = tree[lson].len + tree[rson].len;

/*意义相同,个人感觉更易理解和推广*/
//    if(l == r){
//        if(tree[rt].sum>=1) tree[rt].len = r-l+1;
//        else tree[rt].len = 0;
//        return ;
//    }
//
//    if(tree[rt].sum>=1) tree[rt].len = r-l+1;
//    else tree[rt].len = tree[lson].len + tree[rson].len;

}
void update(int rt, int l, int r, int L, int R, int x)
{
    if(L <= l && R >= r){
        tree[rt].sum += x;
        pushup(rt, l, r);
        return ;
    }
    int mid = (l+r)>>1;
    if(L <= mid) update(lson, l, mid, L, R, x);
    if(R > mid) update(rson, mid+1, r, L, R, x);
    pushup(rt, l, r);
}
int cal(int maxx, int minn, int flag)
{
    int ans = 0, last = 0;
    memset(tree, 0, sizeof(tree));
    if(minn <= 0){
        for(int i=1; i<=num_edge; i++){
            edge[flag][i].l += -minn+1;
            edge[flag][i].r += -minn+1;
        }
        maxx -= minn;
    }
    sort(edge[flag]+1, edge[flag]+num_edge+1);
    for(int i=1; i<=num_edge; i++){
        update(1, 1, maxx, edge[flag][i].l, edge[flag][i].r-1, edge[flag][i].f);  //用点代表的边,所以要-1
        while(edge[flag][i].h == edge[flag][i+1].h && edge[flag][i].f==edge[flag][i+1].f){ //重边
            i++;
            update(1, 1, maxx, edge[flag][i].l, edge[flag][i].r-1, edge[flag][i].f);
        }
        ans += abs(tree[1].len-last);
        last = tree[1].len;
    }
    return ans;
}
int main()
{

    while(~scanf("%d", &n)){
        num_edge=0;
        int maxx1 = -1e9, maxx2 = -1e9, minn1 = 1e9, minn2 = 1e9;
        for(int i=1; i<=n; i++){
            int x1, x2, y1, y2;
            scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
            maxx1 = max(maxx1, x2); minn1 = min(minn1, x1);
            maxx2 = max(maxx2, y2); minn2 = min(minn2, y1);
            addedge(x1, x2, y1, 1, 0);  //横边入
            addedge(y1, y2, x1, 1, 1);  //纵边入
            addedge(x1, x2, y2, -1, 0); //横边出
            addedge(y1, y2, x2, -1, 1); //纵边出
        }
        cout << cal(maxx1, minn1, 0)+cal(maxx2, minn2, 1) << endl;
    }
}

 扫一次的:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 1e5+5;

struct Edge
{
    int l, r, h, f;
    bool operator < (const Edge& e) const {
        return h==e.h ? f>e.f : h<e.h;  //先排入边可以防止上边与下边重合时出错
    }
}edge[maxn];
struct Tree
{
    int sum, num, len;
    bool l, r;
}tree[maxn<<2];

int n, num_edge, ans, last;

void addedge(int l, int r, int h, int f)
{
    edge[++num_edge].l = l, edge[num_edge].r = r;
    edge[num_edge].h = h; edge[num_edge].f = f;
}

void pushup(int rt, int l, int r)
{
    if(tree[rt].sum){
        tree[rt].num = 1;
        tree[rt].len = r-l+1;
        tree[rt].l = tree[rt].r = true;
    }else if(l == r){
        tree[rt].num = 0;
        tree[rt].len = 0;
        tree[rt].l = tree[rt].r = false;
    }else {
        tree[rt].len = tree[lson].len + tree[rson].len;
        tree[rt].num = tree[lson].num + tree[rson].num;
        if(tree[lson].r && tree[rson].l) tree[rt].num--;
        tree[rt].l = tree[lson].l;
        tree[rt].r = tree[rson].r;
    }
}
void update(int rt, int l, int r, int L, int R, int x)
{
    if(L <= l && R >= r){
        tree[rt].sum += x;
        pushup(rt, l, r);
        return ;
    }
    int mid = (l+r)>>1;
    if(L <= mid) update(lson, l, mid, L, R, x);
    if(R > mid) update(rson, mid+1, r, L, R, x);
    pushup(rt, l, r);
}
int main()
{

    while(~scanf("%d", &n)){
        int maxx = -1e9, minn = 1e9;
        num_edge=0, ans=0, last=0;
        memset(tree, 0, sizeof(tree));
        for(int i=1; i<=n; i++){
            int x1, x2, y1, y2;
            scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
            maxx = max(maxx, x2); 
            minn = min(minn, x1);
            addedge(x1, x2, y1, 1);
            addedge(x1, x2, y2, -1);
        }
        if(minn <= 0){      //调整坐标系,最左边调到 x=1
            for(int i=1; i<=num_edge; i++) {
                edge[i].l += -minn+1;
                edge[i].r += -minn+1;
            }
            maxx -= minn;
        }

        sort(edge+1, edge+num_edge+1);
        for(int i=1; i<=num_edge; i++){
            update(1, 1, maxx, edge[i].l, edge[i].r-1, edge[i].f);  //用点代表的边,所以要-1
            while(edge[i].h == edge[i+1].h && edge[i].f==edge[i+1].f){ //重边
                i++;
                update(1, 1, maxx, edge[i].l, edge[i].r-1, edge[i].f);
            }
            ans += abs(tree[1].len-last);
            last = tree[1].len;
            ans += tree[1].num*2 * (edge[i+1].h-edge[i].h);
        }
        cout << ans << endl;
    }
}

题目:Atlantis——http://acm.hdu.edu.cn/showproblem.php?pid=1542

题意:

给n个矩形的左上和右下点的坐标,求覆盖图形的面积。

思路:

若每个点都取整数的话和上一题一样,最后求答案的时候加的是tree[1].len*(edge[i+1].h-edge[i].h)。但题目给的是浮点数,所以需要进行离散化处理,本题的坐标点的取值还不算大,如果坐标点的取值很大但N的值不大也是用离散化处理。其余的就和上一题类似了。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 1e3+5;

struct Edge
{
    double l, r, h; int f;
    bool operator < (const Edge& e) const {
        return h==e.h ? f>e.f : h<e.h;  //先排入边可以防止上边与下边重合时出错
    }
}edge[maxn];
struct Tree
{
    int sum;
    double len;
}tree[maxn<<2];

int n, num_edge;
double ans, x[maxn];

void addedge(double l, double r, double h, int f)
{
    edge[++num_edge].l = l, edge[num_edge].r = r;
    edge[num_edge].h = h; edge[num_edge].f = f;
}
void pushup(int rt, int l, int r)
{
    if(tree[rt].sum) tree[rt].len = x[r+1]-x[l];    //点表示边
    else if(l == r) tree[rt].len = 0;
    else tree[rt].len = tree[lson].len + tree[rson].len;
}
void build(int rt, int l, int r)
{
    tree[rt].len=tree[rt].sum=0;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(lson, l, mid);
    build(rson, mid+1, r);
    pushup(rt, l, r);
}
void update(int rt, int l, int r, int L, int R, int x)
{
    if(L <= l && R >= r){
        tree[rt].sum += x;
        pushup(rt, l, r);
        return ;
    }
    int mid = (l+r)>>1;
    if(L <= mid) update(lson, l, mid, L, R, x);
    if(R > mid) update(rson, mid+1, r, L, R, x);
    pushup(rt, l, r);
}
int main()
{
    int Case=0;
    while(scanf("%d", &n) && n){
        int cnt = 0; ans = 0, num_edge=0;
        for(int i=1; i<=n; i++){
            double x1, x2, y1, y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            addedge(x1, x2, y2, 1);
            addedge(x1, x2, y1, -1);
            x[++cnt] = x1, x[++cnt] = x2;
        }
        sort(x+1, x+cnt+1), cnt = unique(x+1, x+cnt+1)-x-1;    //去重+离散化
        sort(edge+1, edge+num_edge+1);
        build(1, 1, cnt);
        for(int i=1; i<=num_edge; i++){
            int l = lower_bound(x+1, x+cnt+1, edge[i].l)-x;
            int r = lower_bound(x+1, x+cnt+1, edge[i].r)-x;
            update(1, 1, cnt, l, r-1, edge[i].f);    //用点表示边要-1
            ans += tree[1].len * (edge[i+1].h-edge[i].h);                                                                       
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n", ++Case, ans);
    }
}

题目:覆盖的面积——http://acm.hdu.edu.cn/showproblem.php?pid=1255

题意:

给n个矩形的左下和右上点的坐标,求覆盖过两次的图形的面积

思路:

几乎和上题一样,上题是求覆盖的面积,变成覆盖两次或以上的。判断sum的值,如果>1就直接更新覆盖两次的长度,要注意的是当sum==1时,说明区间已经被覆盖,这时加上左右儿子被覆盖过一次的长度就是被覆盖两次的长度,其他维护和上题一样。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 1e5+5;

struct Edge
{
    double l, r, h; int f;
    bool operator < (const Edge& e) const {
        return h==e.h ? f>e.f : h<e.h;  //先排入边可以防止上边与下边重合时出错
    }
}edge[maxn];
struct Tree
{
    int sum;
    double len, len2;
}tree[maxn<<2];

int n, num_edge;
double ans, x[maxn];

void addedge(double l, double r, double h, int f)
{
    edge[++num_edge].l = l, edge[num_edge].r = r;
    edge[num_edge].h = h; edge[num_edge].f = f;
}
void pushup(int rt, int l, int r)
{
    if(tree[rt].sum) tree[rt].len = x[r+1]-x[l];
    else if(l == r) tree[rt].len = 0;
    else tree[rt].len = tree[lson].len + tree[rson].len;

    if(tree[rt].sum>1) tree[rt].len2 = x[r+1]-x[l];    //维护覆盖两次或以上的
    else if(l == r) tree[rt].len2 = 0;
    else if(tree[rt].sum == 1) tree[rt].len2 = tree[lson].len + tree[rson].len;
    else tree[rt].len2 = tree[lson].len2 + tree[rson].len2;
    
/*下方代码和上方意思一样,但我觉得更容易理解,向上推覆盖三次或更多次的思路比较清晰*/
//    if(l == r){          //叶子节点
//        if(tree[rt].sum >= 2){
//            tree[rt].len2 = x[r+1]-x[l];
//            tree[rt].len = 0;
//        }else if(tree[rt].sum >= 1){
//            tree[rt].len = x[r+1]-x[l];
//            tree[rt].len2 = 0;
//        }else tree[rt].len2 = tree[rt].len = 0;
//        return ;
//    }
//    
//    if(tree[rt].sum >= 2){    //非叶子节点
//        tree[rt].len = 0;
//        tree[rt].len2 = x[r+1]-x[l];
//    }else if(tree[rt].sum >= 1){
//        tree[rt].len2 = 0;
//        tree[rt].len2 += (tree[lson].len2 + tree[rson].len2); //2+2>=2
//        tree[rt].len2 += (tree[lson].len + tree[rson].len);   //1+1>=2
//        tree[rt].len = x[r+1]-x[l] - tree[rt].len2;
//    }else {
//        tree[rt].len2 = tree[lson].len2 + tree[rson].len2;
//        tree[rt].len = tree[lson].len + tree[rson].len;
//    }
}
void build(int rt, int l, int r)
{
    tree[rt].len=tree[rt].len2=tree[rt].sum=0;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(lson, l, mid);
    build(rson, mid+1, r);
    pushup(rt, l, r);
}
void update(int rt, int l, int r, int L, int R, int x)
{
    if(L <= l && R >= r){
        tree[rt].sum += x;
        pushup(rt, l, r);
        return ;
    }
    int mid = (l+r)>>1;
    if(L <= mid) update(lson, l, mid, L, R, x);
    if(R > mid) update(rson, mid+1, r, L, R, x);
    pushup(rt, l, r);
}
int main()
{
    int t;
    scanf("%d", &t);
    while(t--){
        scanf("%d", &n);
        int cnt = 0; ans = 0, num_edge=0;
        for(int i=1; i<=n; i++){
            double x1, x2, y1, y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            addedge(x1, x2, y1, 1);
            addedge(x1, x2, y2, -1);
            x[++cnt] = x1, x[++cnt] = x2;
        }
        sort(x+1, x+cnt+1), cnt = unique(x+1, x+cnt+1)-x-1;
        sort(edge+1, edge+num_edge+1);
        build(1, 1, cnt);
        for(int i=1; i<=num_edge; i++){
            int l = lower_bound(x+1, x+cnt+1, edge[i].l)-x;
            int r = lower_bound(x+1, x+cnt+1, edge[i].r)-x;
            update(1, 1, cnt, l, r-1, edge[i].f);
            ans += tree[1].len2 * (edge[i+1].h-edge[i].h);
        }
        printf("%.2f\n", ans);
    }
}

线段树动态开点:

题目:Physical Education Lessons——http://codeforces.com/problemset/problem/915/E

题意:长度为n(1e9)的数组,两种 q(3e5)个操作把区间 [l, r] 的所有数变成0或1,输出每次修改后数组中0的个数

思路:直接开1e9肯定是不行的,q在3e5之内,可以考虑把所有操作离散化。这里采用动态开点方式,即 即用即开,直接看代码也比较容易理解。

#include <bits/stdc++.h>
#define M(x) memset(x, 0, sizeof(x))
using namespace std;
const int maxn = 3e5+4;

int n, m, id, rt;
int lson[maxn*50], rson[maxn*50], sum[maxn*50], lazy[maxn*50];

void pushup(int rt){sum[rt] = sum[lson[rt]] + sum[rson[rt]];}
void pushdown(int rt, int l, int r)
{
    if(lazy[rt] != -1){
        int mid = l+r>>1;
        if(l != r){
            if(lson[rt]==0) lson[rt] = ++ id;
            if(rson[rt]==0) rson[rt] = ++ id;
            lazy[lson[rt]] = lazy[rt];
            lazy[rson[rt]] = lazy[rt];
            sum[lson[rt]] = (mid-l+1)*lazy[rt];
            sum[rson[rt]] = (r-mid)*lazy[rt];
        }
        lazy[rt] = -1;
    }
}
void update(int &rt, int l, int r, int L, int R, int val)
{
    if(!rt) rt = ++ id; //即用即开
    if(L <= l && R >= r){
        sum[rt] = (r-l+1)*val;
        lazy[rt] = val;
        return ;
    }
    pushdown(rt, l, r);
    int mid = l+r>>1;
    if(L <= mid) update(lson[rt], l, mid, L, R, val);
    if(R > mid) update(rson[rt], mid+1, r, L, R, val);
    pushup(rt);
}
int main()
{
    cin >> n >> m;
    id = rt = 0;
    for(int i=1; i<=m; i++){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        update(rt, 1, n, x, y, z==1);
        printf("%d\n",  n-sum[1]);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值