nyoj 856 华山论剑 (并查集or线段树区间点更新)

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



华山论剑

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 3
描述

有n个剑客(编号1~n)相约华山比剑,分 m 次决斗,为了节省时间,每次决斗 编号在[l,r]的剑客一起决斗,然后xi获胜。当进行下一次决斗,失败后的剑客可能再参与到决斗,m 次决斗后可能不止一位获胜者(没有失败过就视为获胜者)。

输入
多组测试数据。
对于每组测试数据,第一行输入n和m。接下来输入m行,每行输入l,r,xi。
2 ≤ n ≤ 3*10^5; 1 ≤ m ≤ 3*10^5,l ≤ xi ≤ r
输出
每组测试数据输出n个数字,数字间用空格隔开。第i个数子表示第一次击败i号剑客的剑客编号,若i号剑客是最后的获胜者,输出0;
样例输入
3 2
1 2 2
1 3 2
样例输出
2 0 2


思路:

一般常规思路是线段树区间set,然后赢得那个点不动,这里学到了区间点更新,就是一个点一个点更新,更新过一次就不再更新了

两种写法:(关键是理解)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 3e5 + 5;
int ans[maxn], lazy[maxn*4], n , m;
void update(int rt, int l, int r, int i, int j, int val)
{
    if(lazy[rt]) return ;
//    if(l == r && l != val && i == l && lazy[rt] == 0)  //注释是我一开始一直re的地方。。l==r就一定要ruturn掉,否则进去下面的程序会re。。。
//    {
//        lazy[rt] = 1;
//        ans[l] = val;
//        return;
//    }
    if(l == r)
    {
        if(l != val && l <= j && l >= i)  
        {
            lazy[rt] = 1;
            ans[l] = val;
        }
        return ;
    }
    int mid = (l+r)/2;
//    if(j <= mid) update(rt*2, l, mid, i, j, val);
//    else if(i > mid) update(rt*2+1, mid+1, r, i, j, val);
//    else
//    {
//        update(rt*2, l, mid, i, mid, val);
//        update(rt*2+1, mid+1, r, mid+1, j, val);
//    }
    if(i <= mid)update(rt*2, l, mid, i, j, val);  //这里就是不断把区间划分成一个一个的小区间
    if(j > mid)update(rt*2+1, mid+1 , r, i, j, val);
    lazy[rt] = lazy[rt*2]&&lazy[rt*2+1];
}
int main()
{
    while(~scanf("%d%d", &n, &m))
    {
        memset(ans, 0, sizeof(ans));
        memset(lazy, 0, sizeof(lazy));
        while(m--)
        {
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            update(1, 1, n, x, y, z);
        }
        for(int i = 1; i <= n; i++)
            printf("%d%c", ans[i], i == n ? '\n' : ' ');
    }
    return 0;
}

第二种写法,更直观一些

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 3e5 + 5;
int ans[maxn], lazy[maxn*4], n , m;
void update(int rt, int l, int r, int i, int j, int val)
{
    if(lazy[rt]) return ; //更新过直接返回
    if(l == r)
    {
        if(l != val && l == i)
        {
            lazy[rt] = 1;
            ans[l] = val;
        }
        return ;
    }
    int mid = (l+r)/2;
    //核心代码:
    if(j <= mid) update(rt*2, l, mid, i, j, val);  
    else if(i > mid) update(rt*2+1, mid+1, r, i, j, val);
    else  //在两边的,要把要求的区间也分开,这样,要求区间最后也就会变成一个
    {
        update(rt*2, l, mid, i, mid, val);
        update(rt*2+1, mid+1, r, mid+1, j, val);
    }
    lazy[rt] = lazy[rt*2]&&lazy[rt*2+1];
}
int main()
{
    while(~scanf("%d%d", &n, &m))
    {
        memset(ans, 0, sizeof(ans));
        memset(lazy, 0, sizeof(lazy));
        while(m--)
        {
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            update(1, 1, n, x, y, z);
        }
        for(int i = 1; i <= n; i++)
            printf("%d%c", ans[i], i == n ? '\n' : ' ');
    }
    return 0;
}

并查集思想维护区间:

每个点的pre 记录的这个点后面离他最近的没更新的。。这样每次在所求区间里,都会直接跳过更新过了的, 如果这个点在x前面 都变成x, 在后面都变成 r+1那个点的pre。。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 3e5 + 5;
int pre[maxn], ans[maxn];
int Find(int x)
{
    return x == pre[x] ? x : pre[x] = Find(pre[x]);
}
int main()
{
    int n, m, l, r, x;
    while(~scanf("%d%d", &n, &m))
    {
        memset(ans, 0, sizeof(ans));
        for(int i = 0; i <= n+1; i++)
            pre[i] = i;
        while(m--)
        {
            scanf("%d%d%d", &l, &r, &x);
            for(int i = Find(l); i <= r; i = Find(i+1))
            {
                if(i == x) continue;
                ans[i] = x;
                if(i < x) pre[i] = x;
                else pre[i] = pre[r+1];
            }
//        cout << 1 << endl;
        }
        for(int i = 1; i <= n; i++)
            printf("%d%c", ans[i], i == n ? '\n' : ' ');
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值