Codeforces Round #207 (Div. 2) C. Knight Tournament

1 篇文章 0 订阅
1 篇文章 0 订阅

比赛的时候线段树写挫了,STL又不会,不敢用,加上B题看错,想不出来,C调了一下比赛就结束了,我就爆零了!D,E都没来得及看!实在是弱到不行了!

写篇报告来纪念一下,主要是回顾一下线段树。

线段树的区间修改查询还是很经典的,对于这题,我们可以用线段树做,倒着想,就相当于一个区间修改问题,每次把在区间里的数改成固定值,对于胜利者要特殊处理,不修改,所以这里有个小技巧,就是把这个区间分成两个区间,胜利者的左边区间(如果存在的话),胜利者的右区间(如果存在的话)。然后分别修改这两个区间,这样胜利者的信息就不会覆盖掉了。区间修改查询的线段树需要一个pushdown操作,详见刘汝佳白书数据结构篇,具体操作我还是要自我回顾一下,就是这样的,每个节点有一个标记表示是否区间被完整覆盖。我们做区间修改的时候,如果发现了当前线段树的节点所对应的区间全在修改区间内,那么直接修改覆盖,如果并不是这样,那么需要考察这个区间是否被标记过,如果被标记过那么,我们就需要把标记往他的儿子节点传一步,(并不需要完全传到叶子,因此时间复杂度还是log级别)然后把这个节点标记清空,递归调用我们想要修改的区间。代码实现如下:

#include<iostream>
#include<algorithm>
#include<set>
#include<cstdlib>
using namespace std;
#define N 300005
#define mem(a) memset(a, 0, sizeof(a))

struct po{
    int l, r, w;
}p[4 * N]; //线段树节点数是总数乘以4

int l[N], r[N], w[N], ql, qr, qw, qq; //倒着存放修改区间

void bld(int x, int l, int r){
    p[x].l = l, p[x].r = r;
    if (l != r){
        int m = (l + r) >> 1;
        bld(2 * x, l, m);
        bld(2 * x + 1, m + 1, r);
    }
}

void pushd(int x){ //pushdown修改子节点的标记
    if (p[x].w > 0){
        p[2 * x].w = p[2 * x + 1].w = p[x].w;
        p[x].w = 0;
    }
}

void upd(int x, int l, int r){
    if (l >= ql && r <= qr){
        p[x].w = qw;
    }
    else{
        int m = (l + r) >> 1;
        pushd(x); //pushdown操作本身自带判别是否有标记
        if (ql <= m) upd(2 * x, l, m); //线段树更新操作
        if (qr > m) upd(2 * x + 1, m + 1, r);
    }
}

int que(int x, int l, int r){
    if (p[x].w > 0) return p[x].w; //询问操作,当前所在去见的标记存在则则这个区间里的数全是这样的不必再往下找了,返回即可
    if (l == r) return p[x].w; //最后的赢家的区间没有标记,所以需要这一步,否则可能查询没有得到结果,RE
    int m = (l + r) >> 1;
    if (qq > m) return que(2 * x + 1, m + 1, r); //当前区间没有标记,递归询问子区间
    else return que(2 * x, l, m);
}

int main(){
    int n, m, i, j, k, x, y;
    ios :: sync_with_stdio(false);
    cin >> n >> m;
    bld(1, 1, n);
    for (i = 0; i < m; ++i){
        cin >> l[i] >> r[i] >> w[i];
    }
    for (i = m - 1; i >= 0; --i){ //倒着做区间修改
        if (l[i] < w[i]) { //分成两个区间,左
            ql = l[i], qr = w[i] - 1, qw = w[i];
            upd(1, 1, n);
        }
        if (w[i] < r[i]){ //右
            ql = w[i] + 1, qr = r[i], qw = w[i];
            upd(1, 1, n);
        }
    }
    for (i = 1; i <= n; ++i) qq = i, w[i] = que(1, 1, n);//人为询问
    for (i = 1; i <= n; ++i){
          cout << w[i] << ' '; //输出,其实可以在上一步直接输出que
    }
    return 0;
}
贴一个用set的代码,STL还是要好好学啊,会方便一些,当然不能过度依赖:

思路大概就是约等于模拟了,set有删除操作,所以删掉以后会节省时间,注意insert,erase操作会使迭代器失效,所以不能在区间里修改的时候就一个一个删除,而要修改完了,把整个去见删除,见代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
#define N 300005

int w[N];

set<int> a;

int main(){
    ios :: sync_with_stdio(false);
    int n, i, j, k, m, z, x, y;
    cin >> n >> m;
    set<int> :: iterator l, r, it;
    for (i = 1; i <= n; ++i) a.insert(i);
    for (i = 0; i < m; ++i) {
        cin >> x >> y >> z;
        l = a.lower_bound(x);
        r = a.upper_bound(y);
        it = l;
        for (;it != r; ++it){
            if (*it != z){
                w[*it] = z;
            }
        }
        a.erase(l, r);
        a.insert(z);
    }
    for (i = 1; i <= n; ++i) cout << w[i] << ' ';
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值