集训数据结构题解

D题: Black And White

题意;q次询问:
1 :询问区间内最大连续1的个数。
2:翻转区间的状态 1 -> 0, 0 –> 1;
维护区间 ,包含左端点的 lm, 包含右边端点的 lm, 区间的答案 mx.
那么首先 mx[rt] 应该由左儿子,右儿子,及左右儿子合并得来

mx[1][rt] = max(mx[1][rt << 1], mx[1][rt << 1 | 1]);//mx[1] 代表连续的是 1  
    mx[1][rt] = max(mx[1][rt], rm[1][rt << 1] + lm[1][rt << 1 | 1]);

lm[rt] ,应该等于 lm[rt<<1], 但是如果lm[rt<<1] == 其区间的长度就要加上 lm[rt<<1|1] 。
求rm[rt] 同 lm[rt]

lm[1][rt] = lm[1][rt << 1];
    if (lm[1][rt << 1] == len[rt << 1]) lm[1][rt] += lm[1][rt << 1 | 1];

同理再维护下 连续0即可。
区间翻转的时候讲 lm[0],lm[1] ,rm[0],rm[1], mx[0],rm[1];,交换即可,再打上标记即可。

询问的时候 ,当 mid 位于 ql 和 qr之间的时候,要考虑 lson 和 rson 合并的问题 同时应当考虑到 lson的 rm 的长度不能超过 ql的位置 ,rson的 lm 的长度不能超过 qr的位置 。

return max(query(l, mid, rt << 1, ql, mid), max(query(mid + 1, r, rt << 1 | 1, mid + 1, qr),
            min(mid - ql + 1, rm[1][rt << 1]) + min(qr - mid, lm[1][rt << 1 | 1])));

主代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e5 + 1010;
using ll = long long;
#define inf 0x7f7f7f7f
int mx[2][maxn << 2], lm[2][maxn << 2], rm[2][maxn << 2], lazy[maxn << 2];
int len[maxn << 2];
inline void maintain(int rt)
{
    mx[1][rt] = max(mx[1][rt << 1], mx[1][rt << 1 | 1]);
    mx[1][rt] = max(mx[1][rt], rm[1][rt << 1] + lm[1][rt << 1 | 1]);
    lm[1][rt] = lm[1][rt << 1];
    if (lm[1][rt << 1] == len[rt << 1]) lm[1][rt] += lm[1][rt << 1 | 1];
    rm[1][rt] = rm[1][rt << 1 | 1];
    if (rm[1][rt << 1 | 1] == len[rt << 1 | 1]) rm[1][rt] += rm[1][rt << 1];

    mx[0][rt] = max(mx[0][rt << 1], mx[0][rt << 1 | 1]);
    mx[0][rt] = max(mx[0][rt], rm[0][rt << 1] + lm[0][rt << 1 | 1]);
    lm[0][rt] = lm[0][rt << 1];
    if (lm[0][rt << 1] == len[rt << 1]) lm[0][rt] += lm[0][rt << 1 | 1];
    rm[0][rt] = rm[0][rt << 1 | 1];
    if (rm[0][rt << 1 | 1] == len[rt << 1 | 1]) rm[0][rt] += rm[0][rt << 1];
}
inline void push(int rt)
{
    if (lazy[rt] == 0)return;
    swap(mx[1][rt << 1], mx[0][rt << 1]);
    swap(lm[1][rt << 1], lm[0][rt << 1]);
    swap(rm[1][rt << 1], rm[0][rt << 1]);

    swap(mx[1][rt << 1 | 1], mx[0][rt << 1 | 1]);
    swap(lm[1][rt << 1 | 1], lm[0][rt << 1 | 1]);
    swap(rm[1][rt << 1 | 1], rm[0][rt << 1 | 1]);
    lazy[rt << 1] ^= 1;
    lazy[rt << 1 | 1] ^= 1;
    lazy[rt] = 0;
}
void update(int l, int r, int rt, int ql, int qr)
{
    if (ql <= l && qr >= r)
    {
        lazy[rt] ^= 1;
        swap(mx[1][rt], mx[0][rt]);
        swap(lm[1][rt], lm[0][rt]);
        swap(rm[1][rt], rm[0][rt]);
        return;
    }
    push(rt);
    int mid = l + r >> 1;
    if (ql <= mid) update(l, mid, rt << 1, ql, qr);
    if (qr > mid) update(mid + 1, r, rt << 1 | 1, ql, qr);
    maintain(rt);
}
int query(int l, int r, int rt, int ql, int qr)
{
    if (ql <= l && qr >= r) return mx[1][rt];
    else
    {
        push(rt);
        int mid = l + r >> 1;
        if (qr <= mid) return query(l, mid, rt << 1, ql, qr);
        else if (ql > mid) return query(mid + 1, r, rt << 1 | 1, ql, qr);
        else return max(query(l, mid, rt << 1, ql, mid), max(query(mid + 1, r, rt << 1 | 1, mid + 1, qr),
            min(mid - ql + 1, rm[1][rt << 1]) + min(qr - mid, lm[1][rt << 1 | 1])));
    }
}
void build(int l, int r, int rt)
{
    lazy[rt] = 0;
    mx[0][rt] = lm[0][rt] = rm[0][rt] = 0;
    mx[1][rt] = lm[1][rt] = rm[1][rt] = 0;
    len[rt] = r - l + 1;
    if (l == r)
    {
        scanf("%d", &mx[1][rt]);
        mx[0][rt] = !mx[1][rt];
        lm[1][rt] = rm[1][rt] = mx[1][rt];
        lm[0][rt] = rm[0][rt] = mx[0][rt];
    }
    else
    {
        int mid = l + r >> 1;
        build(l, mid, rt << 1);
        build(mid + 1, r, rt << 1 | 1);
        maintain(rt);
    }
}
int main()
{
    int n, q;
    while (~scanf("%d", &n))
    {
        build(1, n, 1);
        scanf("%d", &q);
        while (q--)
        {
            int op, l, r;
            scanf("%d %d %d", &op, &l, &r);
            if (op == 0) printf("%d\n", query(1, n, 1, l, r));
            else update(1, n, 1, l, r);
        }
    }
    return 0;
}

E题:Mayor’s posters
题意 求覆盖海报后,能看见的海报的种类数
假定,li <= ri < 1e5 那么就是个sb题,用线段树维护下区间的海报的状态,最后计算一下根节点的信息即可。
现在假定在这个数据范围内你们都会做,那么,我们现在考虑li < 1e7的情况,当然会动态开节点的大佬可忽略。
数据范围大怎么办,离散化对吧 ,不会的请自行百度。然后,我们可以将,离散化后上线段树即可,不过离散化会有个问题 ;对于 这组数据:
3
1 10
1 3
6 10
你会发现少算一部分,原因 离散化后 3 ->6 被认为连续,但是并不连续,由于我们写的线段树维护的是点坐标的值,对于连续区间问题,我们发现假如我们在每个离散化后的不连续区间都插入一个点那么,是不是就可以用那个点来表示那段区间的信息。

             x += 2; y += 2;
            arr[k++] = x;     arr[k++] = y;
            arr[k++] = x + 1; arr[k++] = x - 1;
            arr[k++] = y + 1; arr[k++] = y - 1;

这样就实现了上述功能。
当然,也有其他做法。线段树维护块坐标即可解决离散化后区间连续问题。代码不同之处在于,左儿子 的右端点与右儿子的左端点是一个。

点坐标代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<set>
using namespace std;
const int maxn = 1e5 + 99;
struct WALL {
    int l, r, lazy;
}tr[maxn*4],que[maxn];
int arr[maxn*6], vis[maxn];
void build(int l, int r, int pos)
{
    tr[pos].l = l; tr[pos].r = r;
    tr[pos].lazy = 0;
    if (l == r)return;
    int mid = l + r >> 1;
    build(l, mid, pos <<1);
    build(mid + 1, r, pos << 1 | 1);
}
void push_down(int pos)
{
    if (tr[pos].lazy == 0)return;
    tr[pos << 1].lazy = tr[pos].lazy;
    tr[pos << 1 | 1].lazy = tr[pos].lazy;
    tr[pos].lazy = 0;
}
void update(int ll, int rr, int pos,int wh)
{
    if (tr[pos].l == ll&&tr[pos].r == rr)
    {
        tr[pos].lazy = wh;
        return;
    }
    push_down(pos);
    int mid = tr[pos].l + tr[pos].r >> 1;
    if (mid >= rr)update(ll, rr, pos << 1, wh);
    else if (mid < ll)update(ll, rr, pos << 1 | 1, wh);
    else
    {
        update(ll, mid, pos << 1, wh);
        update(mid + 1, rr, pos << 1 | 1, wh);
    }
}
void push_ans(int l,int r,int pos)
{
    if (l == r)
    {
        arr[l] = tr[pos].lazy;
        return;
    }
    push_down(pos);
    int mid = l + r >> 1;
    push_ans(l, mid, pos << 1);
    push_ans(mid + 1, r, pos << 1 | 1);
}
int main()
{
  //  freopen("D:\\Date\\1.in","r",stdin);
  // freopen("D:\\Date\\1.out","w",stdout);
    int ca;
    scanf("%d", &ca);
    while (ca--)
    {
        int ks;
        cin>>ks;
        int n,k = 1;
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
        {
            int x, y;
            scanf("%d%d", &x, &y);
            x += 2; y += 2;
            arr[k++] = x;     arr[k++] = y;
            arr[k++] = x + 1; arr[k++] = x - 1;
            arr[k++] = y + 1; arr[k++] = y - 1;
            que[i].l = x; que[i].r = y;
            que[i].lazy = i;
        }
        sort(arr + 1, arr + k);
        int len = unique(arr + 1, arr + k) - arr - 1;//去重
        build(1, len, 1);
        for (int i = 1; i <= n; ++i)
        {
            int x = lower_bound(arr + 1, arr + len + 1, que[i].l)-arr;//寻找第i次的海报所对应的位置;
            int y = lower_bound(arr + 1, arr + len + 1, que[i].r)-arr;
            update(x,y, 1, que[i].lazy);
        }
        memset(arr, 0, sizeof arr);
        memset(vis, 0, sizeof vis);
        push_ans(1, len, 1);//tr上的所有标记,push到根节点;
        int ans = 0;
        for(int i = 1;i<=len;++i)
            if (arr[i]!=0&&!vis[arr[i]])
            {
                ans++;
                vis[arr[i]] = 1;
            }
        printf("%d : %d\n",ks,ans);
    }
    return 0;
}
块坐标做法
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int maxn = 2e5+100;
typedef long long ll;
int lazy[maxn<<2];
void push(int rt)
{
    if(lazy[rt]==0) return;
    lazy[rt<<1] = lazy[rt<<1|1] = lazy[rt];
    lazy[rt] = 0;
}
void update(int l,int r,int rt,int ql,int qr,int v)
{
    if(ql<=l && qr>=r)lazy[rt] = v;
    else
    {
        if(l==r-1)return;
        push(rt);
        int mid = l+r>>1;
        if(ql<=mid) update(l,mid,rt<<1,ql,qr,v);
        if(qr>=mid) update(mid,r,rt<<1|1,ql,qr,v);
    }
}
int cnt[maxn];
void getans(int l,int r,int rt)
{
    if(l==r-1)cnt[lazy[rt]]++;
    else
    {
        push(rt);
        int mid = l+r>>1;
        getans(l,mid,rt<<1);
        getans(mid,r,rt<<1|1);
    }
}
vector<int> ve;
pair<int,int>pos[maxn];
int main()
{
    freopen("D:\\Date\\1.in","r",stdin);
    freopen("D:\\Date\\2.out","w",stdout);
    int t,n,q;
    scanf("%d", &t);
    while(t--)
    {
        ve.clear();
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
        {
            scanf("%d %d", &pos[i].first, &pos[i].second);
            pos[i].second++;
            ve.push_back(pos[i].first),ve.push_back(pos[i].second);
        }
        sort(ve.begin(),ve.end());
        ve.erase(unique(ve.begin(),ve.end()),ve.end());
        for(int i = 1;i <= n; ++i)
        {
            pos[i].first = lower_bound(ve.begin(), ve.end(), pos[i].first)-ve.begin()+1;
            pos[i].second = lower_bound(ve.begin(),ve.end(),pos[i].second)-ve.begin()+1;
            update(1,ve.size(),1,pos[i].first,pos[i].second,i);
        }
        getans(1,ve.size(),1);
        int ans = 0;
        for(int i = 1;i <= n; ++i)ans += (cnt[i]>=1);
        for(int i = 0;i <= n*8; ++i)lazy[i] = 0,cnt[i] = 0;
        printf("%d : %d\n",ks,ans);
    }
    return 0;
}

F Turing Tree

详见 :https://blog.csdn.net/mas3399/article/details/78208289

G Super Mario
题意 q次询问给定 l , r, k 求区间内小于等于 k 的个数。
在线做法需要更高级的结构,所以我们尝试用基本数据结构解决,同样的我们发现,询问的顺序对ans不造成影响,那么在考虑,对于询问 l,r,k 。整个序列中只有小于等于 k 的可能会对ans造成贡献。那么可以对原序列排序,并记录其下标,在对询问排序,对值为 k 的询问,只会有 a[i] <= k的产生贡献,那么将小于等于k的a[i],对应 的下标加入到 用树状数组维护。
询问直接算就ok。

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e5+120;
int sum[maxn],n,m,t;
void add(int x,int val)
{
    while(x<=n)sum[x] += val,x += (x&(-x));
}
int getsum(int x)
{
    int ret = 0;
    while(x > 0)ret += sum[x],x -= (x&(-x));
    return ret;
}
pair<int,int>a[maxn];
int ans[maxn];
struct node{int l,r,k,id;}que[maxn];
bool cmp(node a,node b) {
    return a.k<b.k;
}
int main() {
    int ks = 0;
    scanf("%d",&t);
    while(t--)
    {
        memset(sum,0,sizeof sum);
        scanf("%d%d",&n,&m);
        for(int i = 1,x;i <= n; ++i)
        {
            scanf("%d",&x);
            a[i].first = x,a[i].second = i;
        }
        sort(a+1,a+n+1);
        for(int i = 1;i <= m; ++i)
        {
            scanf("%d %d %d",&que[i].l,&que[i].r,&que[i].k);
            que[i].l++,que[i].r++;
            que[i].id = i;
        }
       sort(que+1,que+m+1,cmp);
       int p = 1;
       for(int i = 1;i <= m; ++i)
       {
           while(p<=n&&a[p].first<=que[i].k)
           {
               add(a[p].second,1);
               p++;
           }
           ans[que[i].id] = getsum(que[i].r) - getsum(que[i].l-1);
       }
       printf("Case %d:\n",++ks);
       for(int i = 1;i <= m; ++i)printf("%d\n",ans[i]);
    }
    return 0;
}

H 题 Seq 维护序列seq

题意:给定一个区间,两种操作,一是区间加,第二是区间乘,然后询问区间和
解法:考虑设置两个lazy标记,add表示加,mul表示乘,由于加法和乘法存在优先级关系,所以
在pushdown的时候一定要考虑到优先级。先假设一个区间add[rt]=x,mul[rt]=y,现在对该区间加z
那么add[rt]=x+y,mul[rt]不变,对该区间乘z,那么add[rt]=xz,mul[rt]=yz。在标记下传的时候
父亲节点的mul标记将会对儿子节点的add和mul标记都产生影响,因此我们先下传mul标记,再下传add标记。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=1e5+5;
int n,q;
LL p;
LL tree[maxn<<2],mul[maxn<<2],add[maxn<<2];
inline void pushup(int rt)
{
    tree[rt]=tree[rt<<1]+tree[rt<<1|1];
    tree[rt]%=p;
}
inline void pushdown(int rt,int L,int R)
{
    if(mul[rt]==1&&add[rt]==0) return;
    int len=R-L+1;
    mul[rt<<1]=mul[rt<<1]*mul[rt]%p;
    add[rt<<1]=(add[rt<<1]*mul[rt]%p+add[rt])%p;
    tree[rt<<1]=(tree[rt<<1]*mul[rt]%p+add[rt]*(len-len/2)%p)%p;

    mul[rt<<1|1]=mul[rt<<1|1]*mul[rt]%p;
    add[rt<<1|1]=(add[rt<<1|1]*mul[rt]%p+add[rt])%p;
    tree[rt<<1|1]=(tree[rt<<1|1]*mul[rt]%p+add[rt]*(len/2)%p)%p;

    mul[rt]=1;
    add[rt]=0;
}
inline void build(int rt,int L,int R)
{
    mul[rt]=1;
    add[rt]=0;
    if(L==R){
        scanf("%lld",&tree[rt]);
        tree[rt]%=p;
        return;
    }
    int mid=L+R>>1;
    build(rt<<1,L,mid);
    build(rt<<1|1,mid+1,R);
    pushup(rt);
}
inline void update(int rt,int L,int R,int l,int r,int op,LL c)
{
    if(l<=L&&r>=R){
        if(op==1){//mul
            mul[rt]=mul[rt]*c%p;
            add[rt]=add[rt]*c%p;
            tree[rt]=tree[rt]*c%p;
        }else{//add
            add[rt]=(add[rt]+c)%p;
            tree[rt]=(tree[rt]+c*(R-L+1))%p;
        }
        return;
    }
    pushdown(rt,L,R);
    int mid=L+R>>1;
    if(r<=mid) update(rt<<1,L,mid,l,r,op,c);
    else if(l>mid) update(rt<<1|1,mid+1,R,l,r,op,c);
    else{
        update(rt<<1,L,mid,l,mid,op,c);
        update(rt<<1|1,mid+1,R,mid+1,r,op,c);
    }
    pushup(rt);
}
inline LL getans(int rt,int L,int R,int l,int r)
{
    if(l<=L&&r>=R) return tree[rt]%p;
    int mid=L+R>>1;
    pushdown(rt,L,R);
    if(r<=mid) return getans(rt<<1,L,mid,l,r);
    else if(l>mid) return getans(rt<<1|1,mid+1,R,l,r);
    else{
        return (getans(rt<<1,L,mid,l,mid)+getans(rt<<1|1,mid+1,R,mid+1,r))%p;
    }
}
int main()
{
    scanf("%d%lld",&n,&p);
    build(1,1,n);
    scanf("%d",&q);
    while(q--){
        int op,l,r;
        LL c;
        scanf("%d",&op);
        if(op==1||op==2){
            scanf("%d%d%lld",&l,&r,&c);
            update(1,1,n,l,r,op,c%p);
        }else{
            scanf("%d%d",&l,&r);
            LL ans=getans(1,1,n,l,r);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

I 题 : Level up 详见:https://blog.csdn.net/mas3399/article/details/78240108
P题 : Tunnel Warfare
题解:
可以用set维护下。
线段树的做法,对每个点维护他的最左边的那个没有被摧毁的村庄 pl[i],和最右边的那个没有被摧毁村庄 pr[i[ 。
假定现在摧毁 第i 个村庄,那将 [pl[i] , i-1] 区间的 pr 值更新为i-1,[ i+1,pr[i] ] 区间的 pl值更新为i-1,并且将pl[i] -> i+1,pr[i] -> i-1;
重建:将 [pl[i-1],i]区间的 pr 的值 -> pr[i+1], [i,pr[i+1]] 区间的 pl 的值 -> ql[i-1]。
答案为 max(0,pr[i] - pl[i]+1)
注意要多组输入

//多组

#include <bits/stdc++.h>

using namespace std;
const int maxn = 5e4+1010;
using ll = long long;
int pot[2][maxn<<2]; //0 L, 1 R
int lazy[2][maxn<<2];
void push(int rt,int op)
{
    if(lazy[op][rt]==-1) return;
    lazy[op][rt<<1] = lazy[op][rt<<1|1] = lazy[op][rt];
    pot[op][rt<<1] = pot[op][rt<<1|1] = lazy[op][rt];
    lazy[op][rt] = -1;
}
void update(int l,int r,int rt,int ql,int qr,int v,int op)
{
    if(ql>qr)return;
    if(ql<=l && qr>=r) pot[op][rt] = v,lazy[op][rt] = v;
    else
    {
        push(rt,op);
        int mid = l+r>>1;
        if(ql<=mid) update(l,mid,rt<<1,ql,qr,v,op);
        if(qr>mid) update(mid+1,r,rt<<1|1,ql,qr,v,op);
    }
}
pair<int,int> query(int l,int r,int rt,int p)
{
    if(l==r)return {pot[0][rt],pot[1][rt]};
    else
    {
        push(rt,0),push(rt,1);
        int mid = l+r>>1;
        if(p<=mid) return query(l,mid,rt<<1,p);
        else return query(mid+1,r,rt<<1|1,p);
    }
}
int vig[maxn],top;
int main()
{
    int n,m;
    while(~scanf("%d %d", &n, &m))
    {
        memset(lazy,-1,sizeof lazy);
        for(int i = 1; i < 4*n; ++i)
            pot[0][i] = 1,pot[1][i] = n;
        top = 0;
        while(m--)
        {
            char op[10];
            int p;
            scanf("%s",op);
            if(op[0]=='D')
            {
                scanf("%d",&p);
                vig[++top] = p;
                pair<int,int>ret = query(1,n,1,p);
                update(1,n,1,ret.first,p-1,p-1,1);
                update(1,n,1,p+1,ret.second,p+1,0);
                update(1,n,1,p,p,p+1,0);
                update(1,n,1,p,p,p-1,1);
            }
            if(op[0]=='Q')
            {
                scanf("%d",&p);
                pair<int,int>ret = query(1,n,1,p);
                int len = ret.second - ret.first + 1;
                len = max(len,0);
                printf("%d\n",len);
            }
            if(op[0]=='R')
            {
                if(top==0)continue;
                p = vig[top],--top;
                pair<int,int> ret = {1,n};
                if(p==1)ret.first = 1;
                else ret.first = query(1,n,1,p-1).first;
                if(p==n) ret.second = n;
                else ret.second = query(1,n,1,p+1).second;
                update(1,n,1,p,p,ret.first,0);
                update(1,n,1,p,p,ret.second,1);
                update(1,n,1,ret.first,p-1,ret.second,1);
                update(1,n,1,p+1,ret.second,ret.first,0);
                ret = query(1,n,1,p);
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值