线段树套题

A - 敌兵布阵

题目链接
在这里插入图片描述
在这里插入图片描述

#include <algorithm>
#include <bitset>
#include <cassert>
#include <cctype>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <functional>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <string>
#include <vector>
using namespace std;
const int N = 50010;
int abc;
int n, x, y, t;
int cnt = 1;
char s[10];
int input[N];
struct node {
    int l, r, sum, lazy /*懒惰标记,下文会提到*/;
    node() { l = r = sum = lazy = 0; }    //给每一个元素赋初值
} tree[150000];                                // N为总节点数
inline void build(int i, int l, int r) {  //递归建树
    tree[i].l = l;
    tree[i].r = r;
    if (l == r) {  //如果这个节点是叶子节点
        tree[i].sum = input[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(i * 2, l, mid);  //分别构造左子树和右子树
    build(i * 2 + 1, mid + 1, r);
    tree[i].sum =
        tree[i * 2].sum + tree[i * 2 + 1].sum;  //刚才我们发现的性质return ;
}
inline int search(int i, int l, int r) {
    if (tree[i].l >= l &&
        tree[i].r <=
            r)  //如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
        return tree[i].sum;
    if (tree[i].r < l || tree[i].l > r)
        return 0;  //如果这个区间和目标区间毫不相干,返回0
    int s = 0;
    if (tree[i * 2].r >= l)
        s += search(i * 2, l,
                    r);  //如果这个区间的左儿子和目标区间又交集,那么搜索左儿子
    if (tree[i * 2 + 1].l <= r)
        s += search(i * 2 + 1, l,
                    r);  //如果这个区间的右儿子和目标区间又交集,那么搜索右儿子
    return s;
}

inline void add(int i, int dis, int k) {
    if (tree[i].l == tree[i].r) {  //如果是叶子节点,那么说明找到了
        tree[i].sum += k;
        return;
    }
    if (dis <= tree[i * 2].r)
        add(i * 2, dis, k);  //在哪往哪跑
    else
        add(i * 2 + 1, dis, k);
    tree[i].sum = tree[i * 2].sum + tree[i * 2 + 1].sum;  //返回更新
    return;
}

int main() {
    cin >> t;
    while (t--) {
        printf("Case %d:\n", cnt++);
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
            scanf("%d", &input[i]);
        build(1, 1, n);
        while (1) {
            scanf("%s", s);
            if (s[0] == 'E')
                break;
            scanf("%d%d", &x, &y);
            if (s[0] == 'Q')
                cout << search(1, x, y) << endl;
            else if (s[0] == 'A')
                add(1, x, y);
            else if (s[0] == 'S')
                add(1, x, -y);
        }
    }

    return 0;
}

这里也是套用了单点修改,区间查询的模板。

B - I Hate It

题目链接
在这里插入图片描述
在这里插入图片描述

#include <algorithm>
#include <bitset>
#include <cassert>
#include <cctype>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <functional>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <string>
#include <vector>
using namespace std;
const int N = 200050;

int n, x, y, t, m;
char s[10];
int input[N];
struct node {
    int l, r, sum, lazy /*懒惰标记,下文会提到*/;
    node() { l = r = sum = lazy = 0; }    //给每一个元素赋初值
} tree[525000];                           // N为总节点数,
inline void build(int i, int l, int r) {  //递归建树
    tree[i].l = l;
    tree[i].r = r;
    if (l == r) {  //如果这个节点是叶子节点
        tree[i].sum = input[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(i * 2, l, mid);  //分别构造左子树和右子树
    build(i * 2 + 1, mid + 1, r);
    tree[i].sum =
        max(tree[i * 2].sum, tree[i * 2 + 1].sum);  //刚才我们发现的性质return ;
}
inline int search(int i, int l, int r) {
    if (tree[i].l >= l &&
        tree[i].r <=
            r)  //如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
        return tree[i].sum;
    if (tree[i].r < l || tree[i].l > r)
        return 0;  //如果这个区间和目标区间毫不相干,返回0
    int s = 0, s2 = 0;
    if (tree[i * 2].r >= l)
        s = search(i * 2, l,
                   r);  //如果这个区间的左儿子和目标区间又交集,那么搜索左儿子
    if (tree[i * 2 + 1].l <= r)
        s2 = search(i * 2 + 1, l,
                    r);  //如果这个区间的右儿子和目标区间又交集,那么搜索右儿子
    return max(s, s2);
}

inline void update(int i, int dis, int k) {
    if (tree[i].l == tree[i].r) {  //如果是叶子节点,那么说明找到了
        tree[i].sum = k;
        return;
    }
    if (dis <= tree[i * 2].r)
        update(i * 2, dis, k);  //在哪往哪跑
    else
        update(i * 2 + 1, dis, k);
    tree[i].sum = max(tree[i * 2].sum, tree[i * 2 + 1].sum);
    return;
}

int main() {
    while (scanf("%d%d", &n, &m) != EOF) {
        for (int i = 1; i <= n; ++i)
            scanf("%d", &input[i]);
        build(1, 1, n);
        while (m--) {
            scanf("%s", s);
            scanf("%d%d", &x, &y);
            if (s[0] == 'U')
                update(1, x, y);
            else if (s[0] == 'Q')
                cout << search(1, x, y) << endl;
        }
    }

    return 0;
}

这里套用了单点修改,区间查询的模板,tree数组里面的sum在这里是表示区间最大值的意思(懒得改了),其他改动不大。

C - A Simple Problem with Integers

题目链接
在这里插入图片描述
在这里插入图片描述

#include <algorithm>
#include <bitset>
#include <cassert>
#include <cctype>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <functional>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <string>
#include <vector>

using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 5;
ll tree[MAXN << 2], mark[MAXN << 2], n, m, A[MAXN];
void push_down(int p, int len) {
    tree[p << 1] += mark[p] * (len - len / 2);
    mark[p << 1] += mark[p];
    tree[p << 1 | 1] += mark[p] * (len / 2);
    mark[p << 1 | 1] += mark[p];
    mark[p] = 0;
}
void build(int p = 1, int cl = 1, int cr = n) {
    if (cl == cr) {
        tree[p] = A[cl];
        return;
    }
    int mid = (cl + cr) >> 1;
    build(p << 1, cl, mid);
    build(p << 1 | 1, mid + 1, cr);
    tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n) {
    if (cl >= l && cr <= r)
        return tree[p];
    push_down(p, cr - cl + 1);
    ll mid = (cl + cr) >> 1, ans = 0;
    if (mid >= l)
        ans += query(l, r, p << 1, cl, mid);
    if (mid < r)
        ans += query(l, r, p << 1 | 1, mid + 1, cr);
    return ans;
}
void update(int l, int r, int d, int p = 1, int cl = 1, int cr = n) {
    if (cl >= l && cr <= r) {
        tree[p] += d * (cr - cl + 1), mark[p] += d;
        return;
    }
    push_down(p, cr - cl + 1);
    int mid = (cl + cr) >> 1;
    if (mid >= l)
        update(l, r, d, p << 1, cl, mid);
    if (mid < r)
        update(l, r, d, p << 1 | 1, mid + 1, cr);
    tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
int main() {
    while (scanf("%d%d", &n, &m) != EOF) {
        for (int i = 1; i <= n; ++i)
            cin >> A[i];
        build();
        while (m--) {
            int l, r, d;
            string s;
            cin >> s >> l >> r;
            if (s[0] == 'C')
                cin >> d, update(l, r, d);
            else
                cout << query(l, r) << '\n';
        }
    }

    return 0;
}

比较标准的模板,区间修改+区间查询。

D - Hotel

题目链接
在这里插入图片描述
在这里插入图片描述

#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;

#define lson(x) ((x << 1) + 1)
#define rson(x) ((x << 1) + 2)

const int N = 50005;

int n, m;

struct Node {
    // lsum是从左到右的可出租的房子的最大值,同理rsum是从右到左,sum是最大块块空房间
    //的大小,sumv是指最大块空房间的起始位置(下标)
    int l, r, lsum, rsum, sum, sumv, lazy;
    int size() { return r - l + 1; }
    void gao(int v) {
        lazy = v;
        if (v)
            lsum = rsum = sum = 0;
        else
            lsum = rsum = sum = r - l + 1;
        sumv = l;
    }
} node[N * 4];

void pushup(int x) {
    if (node[lson(x)].lsum == node[lson(x)].size())
        node[x].lsum = node[lson(x)].lsum + node[rson(x)].lsum;
    else
        node[x].lsum = node[lson(x)].lsum;

    if (node[rson(x)].rsum == node[rson(x)].size())
        node[x].rsum = node[lson(x)].rsum + node[rson(x)].rsum;
    else
        node[x].rsum = node[rson(x)].rsum;

    node[x].sum = node[lson(x)].sum;
    node[x].sumv = node[lson(x)].sumv;

    if (node[x].sum < node[lson(x)].rsum + node[rson(x)].lsum) {
        node[x].sum = node[lson(x)].rsum + node[rson(x)].lsum;
        node[x].sumv = node[lson(x)].r - node[lson(x)].rsum + 1;
    }

    if (node[x].sum < node[rson(x)].sum) {
        node[x].sum = node[rson(x)].sum;
        node[x].sumv = node[rson(x)].sumv;
    }
}

void pushdown(int x) {
    if (node[x].lazy != -1) {
        node[lson(x)].gao(node[x].lazy);
        node[rson(x)].gao(node[x].lazy);
        node[x].lazy = -1;
    }
}

void build(int l, int r, int x = 0) {
    node[x].l = l;
    node[x].r = r;
    node[x].lazy = -1;
    if (l == r) {
        node[x].lsum = node[x].rsum = node[x].sum = 1;
        return;
    }
    int mid = (l + r) / 2;
    build(l, mid, lson(x));
    build(mid + 1, r, rson(x));
    pushup(x);
}

void add(int l, int r, int v, int x = 0) {
    if (node[x].l >= l && node[x].r <= r) {
        node[x].gao(v);
        return;
    }
    int mid = (node[x].l + node[x].r) / 2;
    pushdown(x);
    if (l <= mid)
        add(l, r, v, lson(x));
    if (r > mid)
        add(l, r, v, rson(x));
    pushup(x);
}

int query(int v, int x = 0) {
    if (node[x].l == node[x].r) {
        if (node[x].sum >= v)
            return node[x].sumv;
        return 0;
    }
    pushdown(x);
    int ans = 0;
    if (node[lson(x)].sum >= v)
        ans = query(v, lson(x));
    else if (node[lson(x)].rsum + node[rson(x)].lsum >= v)
        ans = node[lson(x)].r - node[lson(x)].rsum + 1;
    else if (node[rson(x)].sum >= v)
        ans = query(v, rson(x));
    pushup(x);
    return ans;
}

int main() {
    while (~scanf("%d%d", &n, &m)) {
        build(1, n);
        int op, a, b;
        while (m--) {
            scanf("%d%d", &op, &a);
            if (op == 2) {
                scanf("%d", &b);
                add(a, a + b - 1, 0);
            } else {
                int tmp = query(a);
                printf("%d\n", tmp);
                if (tmp == 0)
                    continue;
                add(tmp, tmp + a - 1, 1);
            }
        }
    }
    return 0;
}

题解:是用到了线段树,不过加了很多东西,比如用pushup去分析本区间的最大连续空房间有三种可能,一是最大连续空房间完全在左区间,二是完全在右区间,三是刚好垮了两个区间,并记录起始下标。

监视任务

题目链接
在这里插入图片描述
在这里插入图片描述

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <algorithm>
#include <functional>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
#define ll long long
#define inf 1000000000
#define mod 1000000007
#define maxn 500005
#define lowbit(x) (x & -x)
#define eps 1e-9
struct node {
    int l, r, k;
} a[maxn * 2];
int n, m, sum[maxn * 5], lazy[maxn * 5];
bool comp(node a, node b) {
    return a.r < b.r;
}
void pushdown(int l, int r, int id) {
    if (lazy[id]) {
        int mid = (l + r) / 2;
        lazy[id * 2] = 1;
        sum[id * 2] = mid - l + 1;
        lazy[id * 2 + 1] = 1;
        sum[id * 2 + 1] = r - mid;
        lazy[id] = 0;
    }
}
int update(int id, int L, int R, int l, int r, int val) {
    if (val <= 0)
        return 0;
    if (L >= l && R <= r) {
        if (R - L + 1 - sum[id] <=
            val) {  //需要布置监视点 (即便当前全部布满也不能达到要求)
            val -= R - L + 1 - sum[id];  //还需要的监视点
            lazy[id] = 1;
            sum[id] = R - L + 1;  //全部布满
            return val;           //还需要布满的数量
        }
    }
    pushdown(L, R, id);
    int mid = (L + R) / 2;
    if (r > mid)
        val = update(id * 2 + 1, mid + 1, R, l, r, val);
    if (l <= mid)
        val = update(id * 2, L, mid, l, r, val);
    sum[id] = sum[id * 2] + sum[id * 2 + 1];
    return val;
}
int query(int id, int L, int R, int l, int r) {
    if (L >= l && R <= r)
        return sum[id];
    pushdown(L, R, id);
    int mid = (L + R) / 2, res = 0;
    if (l <= mid)
        res += query(id * 2, L, mid, l, r);
    if (r > mid)
        res += query(id * 2 + 1, mid + 1, R, l, r);
    return res;
}
int main(void) {
    int i;
    scanf("%d%d", &n, &m);
    for (i = 1; i <= m; i++)
        scanf("%d%d%d", &a[i].l, &a[i].r, &a[i].k);
    sort(a + 1, a + m + 1, comp);
    for (i = 1; i <= m; i++) {
        int w = query(1, 1, n, a[i].l, a[i].r);

        update(1, 1, n, a[i].l, a[i].r, a[i].k - w);
    }

    printf("%d\n", query(1, 1, n, 1, n));
    return 0;
}

题解:用了线段树+贪心,从左往右处理,优先在防卫右边的点,优先处理右端点在前面的区间,所以排序是按照右端点越小越在前。

Sum

题目链接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <bits/stdc++.h>
#define il inline
#define lt p<<1
#define rt p<<1|1
using namespace std;

typedef long long ll;

int const N=1e5+100;
ll const MOD=1e9+7;

ll n,m,op,x,y,l,r;
ll a[N],b[N];

struct Segment_tree
{
	int l,r;
	ll sum[35];
	
}T[N<<2];

il void pushup(int p)
{
	for(int i=0;i<=32;i++)
	{
		T[p].sum[i]=(T[lt].sum[i]+T[rt].sum[i])%MOD;
	}
}

il void build(int l,int r,int p)
{
	T[p].l=l,T[p].r=r;
	if(l==r)
	{
		for(int i=0;i<=32;i++)
		{
			T[p].sum[i]=a[l]%2;
			a[l]/=2;
		}
		return ;
	}
	int m=(l+r)>>1;
	build(l,m,lt),build(m+1,r,rt);
	pushup(p);
}

il void update(int pos,int val,int p)
{
	if(T[p].l==T[p].r)
	{
		for(int i=0;i<=32;i++)
		{
			T[p].sum[i]=val%2;
			val/=2;
		}
		return ;
	}
	int m=(T[p].l+T[p].r)>>1;
	if(pos<=m)update(pos,val,lt);
	else update(pos,val,rt);
	pushup(p);
}

il Segment_tree query(int l,int r,int p)
{
	if(l<=T[p].l&&T[p].r<=r)
	{
		return T[p];
	}
	int m=(T[p].l+T[p].r)>>1;
	Segment_tree a,b,c;
	memset(a.sum,0,sizeof(a.sum));
	memset(b.sum,0,sizeof(b.sum));
	if(l<=m)a=query(l,r,lt);
	if(r>m)b=query(l,r,rt);
	for(int i=0;i<=32;i++)
	{
		c.sum[i]=(a.sum[i]+b.sum[i])%MOD;
	}
	return c;
}



int main()
{
	b[0]=1;
	for(int i=1;i<N;i++)
	{
		b[i]=(b[i-1]<<1)%MOD;
	}
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	scanf("%lld",&m);
	build(1,n,1);
	while(m--)
	{
		scanf("%lld",&op);
		if(op==2)
		{
			scanf("%lld%lld",&l,&r);
			Segment_tree ans=query(l,r,1);
			ll res=0;
			for(int i=0;i<=32;i++)
			{
				res=(res+(b[i]*(b[ans.sum[i]]-1))%MOD)%MOD;
			}
			printf("%lld\n",res%MOD);
		}
		else
		{
			scanf("%lld%lld",&x,&y);
			update(x,y,1);
		}
	}
	return 0;
} 

题解:我们如果直接计算操作2的话会很困难,我们可以直接考虑一个数的二进制位对答案做出的贡献,显然二进制位为0时就不会有任何贡献,当我们知道所有的数在二进制第i位上的总数目为x时,我们就可以直接计算出所有的子集所做出的贡献,我们会想到,会有2的x次幂减1个子集,然后再乘上个位权(1LL<<i),有一个地方不要忘记,就是每次对于1操作,我们不要忘记更新a数组里的元素。我们能够发现,and操作的原理是,全1得1,每个数加起来,只有1做出了贡献,那么我们维护的是1的个数。首先把每个数拆成0和1,求和的时候,把1个数加起来。
下面图片帮助理解:题解链接

The Trip On Abandoned Railway

题目链接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std;

const int maxx=1e5+100;
struct node{
	int l;
	int r;
	ll sum;
	ll lazy1;
}p[maxx<<2];
ll a[maxx];
ll c[maxx];
int n,m;ll d;
/*-----------树状数组------------*/
inline int lowbit(int x){return x & -x;}
inline void add(int cur,ll v)
{
	while(cur<maxx)
	{
		c[cur]=(c[cur]+v)%mod;
		cur+=lowbit(cur);
	}
}
inline ll Query(int cur)
{
	ll ans=0;
	while(cur>0)
	{
		ans=(ans+c[cur])%mod;
		cur-=lowbit(cur);
	}
	return ans;
}
/*------------线段树-------------*/
inline void pushup(int cur)
{
	p[cur].sum=(p[cur<<1].sum+p[cur<<1|1].sum);
}
inline void pushdown(int cur)
{
	if(p[cur].lazy1)
	{
		p[cur<<1].lazy1=(p[cur<<1].lazy1+p[cur].lazy1);
		p[cur<<1|1].lazy1=(p[cur<<1|1].lazy1+p[cur].lazy1);
		p[cur<<1].sum=(p[cur<<1].sum+(p[cur<<1].r-p[cur<<1].l+1)*1ll*p[cur].lazy1);
		p[cur<<1|1].sum=(p[cur<<1|1].sum+(p[cur<<1|1].r-p[cur<<1|1].l+1)*1ll*p[cur].lazy1);
		p[cur].lazy1=0;
	}
}
inline void build(int l,int r,int cur)
{
	p[cur].l=l;
	p[cur].r=r;
	p[cur].lazy1=p[cur].sum=0;
	if(l==r) return ;
	int mid=l+r>>1;
	build(l,mid,cur<<1);
	build(mid+1,r,cur<<1|1);
}
inline void update(int l,int r,ll v,int cur)
{
	int L=p[cur].l;
	int R=p[cur].r;
	if(l<=L&&R<=r)
	{
		p[cur].lazy1=(p[cur].lazy1+v);
		p[cur].sum=(p[cur].sum+(R-L+1)*v);
		return ;
	}
	int mid=L+R>>1;
	pushdown(cur);
	if(r<=mid) update(l,r,v,cur<<1);
	else if(l>mid) update(l,r,v,cur<<1|1);
	else update(l,mid,v,cur<<1),update(mid+1,r,v,cur<<1|1);
	pushup(cur);
}
inline ll query(int l,int r,int cur)
{
	int L=p[cur].l;
	int R=p[cur].r;
	if(l<=L&&R<=r) return p[cur].sum;
	pushdown(cur);
	int mid=L+R>>1;
	ll ans;
	if(r<=mid) return query(l,r,cur<<1);
	else if(l>mid) return query(l,r,cur<<1|1);
	else return (query(l,mid,cur<<1)+query(mid+1,r,cur<<1|1));
	return ans;
}
int main()
{
	int t,op,x;ll y;
	scanf("%d",&t);
	while(t--)
	{
		memset(c,0,sizeof(c));
		scanf("%d%d%lld",&n,&m,&d);
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		build(1,n,1);
		while(m--)
		{
			scanf("%d",&op);
			if(op==1)
			{
				scanf("%d%lld",&x,&y);
				add(x,y);
				update(x+1,n,1,1);
			}
			else
			{
				scanf("%d",&x);
				ll ans=(Query(x)+query(1,x,1)*d*1ll+a[x]);
				printf("%lld\n",ans%mod);
				a[x]-=ans;
			}
		}
	}
	return 0;
}

题解:用树状数组去存+k的操作,用线段树去存+k*d的操作。题解链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值