一类连续区间更新问题(1)

题目链接

  • 样例大意

输入a个人,b组数据

每行输入x,y,z三个数,表示x-y中z是获胜者(即打败了其他的人)

输出每个人被谁打败,最终获胜者输出0


  • 题目特点

区间更新问题,但是一个点只会被更新一次,线段树可以解,但是太过麻烦。问题的关键是如何快算的更新区间,即跳过更新过的区间

  • 思路(解决更新的问题)

  1. 既然每次都是成段更新,而更新过的区间不用再更新,那么每次都二分一下当前区间,如果区间内都是已更新过的点,那么直接跳过;如果区间内只有一个需要更新的点,更新之。使用树状数组,1表示待更新,0表示更新过
    代码如下(Time:514 ms   Memory:2352 KB)
    const int MAXN = 300010;
    
    int n[MAXN], ans[MAXN];
    
    inline int lowbit(int x) { return x & -x; }
    
    inline int sum(int p)
    {
        int ret = 0;
        while (p > 0)
        {
            ret += n[p];
            p -= lowbit(p);
        }
        return ret;
    }
    
    inline void add(int p, int v)
    {
        while (p < MAXN)
        {
            n[p] += v;
            p += lowbit(p);
        }
    }
    
    inline void update(int l, int r, int v)
    {
        int ls = sum(l - 1), rs = sum(r);
        if (rs == ls) return;
        if (l == r)
        {
            add(l, -1);
            ans[l] = v;
            return;
        }
        int m = (l + r) >> 1;
        update(l, m, v);
        update(m + 1, r, v);
    }
    
    int main()
    {
    //    freopen("input.txt", "r", stdin);
        int a, b, x, y, z;
        RII(a, b);
        FE(i, 1, a) add(i, 1);
        REP(i, b)
        {
            RIII(x, y, z);
            update(x, y, z);
            add(z, 1);
            ans[z] = 0;
        }
        FE(i, 1, a)
        {
            if (i != 1) cout << ' ';
            cout << ans[i];
        }
        cout << endl;
        return 0;
    }

  2. 利用每个点只更新一次的特点,使用set数据结构,初始的时候将每个点都插入集合,之后更新过的点就从set中删除
    代码如下(Time:296 ms   Memory:10776 KB)
    注意:st.erase(it)最好不要使用,会出错导致RE。应该使用st.erase(it++)
    const int MAXN = 300010;
    
    set<int> st;
    set<int>::iterator it;
    int ans[MAXN];
    
    int main()
    {
    //    freopen("input.txt", "r", stdin);
        int a, b, x, y, z;
        RII(a, b);
        FE(i, 1, a) st.insert(i);
        st.insert(a + 10);
        REP(i, b)
        {
            RIII(x, y, z);
            it = st.lower_bound(x);
            while ((*it) <= y)
            {
                ans[*it] = z;
                st.erase(it++);
            }
    
            st.insert(z);
            ans[z] = 0;
        }
        FE(i, 1, a)
        {
            if (i != 1) cout << ' ';
            cout << ans[i];
        }
        cout << endl;
        return 0;
    }
    

  3. 使用跳过策略。每个点都记录一下它下一个没有更新的点是谁,使用的数据结构是并查集
    代码如下(Time:171 ms   Memory:4208 KB)
    const int MAXN = 300010;
    
    int father[MAXN], ans[MAXN];
    
    inline int findFather(int n)
    {
        if (father[n] == n) return n;
        return father[n] = findFather(father[n]);
    }
    
    inline void merge(int a, int b)
    {
        int fa = findFather(a), fb = findFather(b);
        father[fa] = fb;
    }
    
    void solve(int x, int y, int v)
    {
        if (x > y) return;
        int t = findFather(x);
        while (t <= y)
        {
            int next = findFather(t);
            if (t == next)
            {
                ans[t] = v;
                merge(t, t + 1);
                t++;
            }
            else
            {
                t = next;
            }
        }
    }
    
    int main()
    {
    //    freopen("input.txt", "r", stdin);
        int a, b, x, y, z;
        RII(a, b);
        FE(i, 1, a + 1) father[i] = i;
        REP(i, b)
        {
            RIII(x, y, z);
            solve(x, z - 1, z);
            solve(z + 1, y, z);
        }
        FE(i, 1, a)
        {
            if (i != 1) cout << ' ';
            cout << ans[i];
        }
        cout << endl;
        return 0;
    }

  • 总结:
    这道题目的另一个特点是,需要更新的区间可能已经更新过了。如果没有这个特点,使用带Hash的双向链表也是可以的
    总的来看,如果不是特意去卡时间,这类问题使用set来解决是上策,代码复杂度十分的低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值