洛谷题单指南-分治与倍增-P7562 [JOISC 2021 Day4] イベント巡り 2 (Event Hopping 2)

原题链接:https://www.luogu.com.cn/problem/P7562

题意解读:每个活动都是一个区间,在n个区间里选k个没有交叉或者包含关系的区间,使得k个区间编号的字典序最小。

解题思路:

此题本质上是一个区间选择问题,遍历每一个区间,如果这个区间能选就一定优先选先该区间,这样能保证字典序最小。

因此,要解决的关键问题就是:如何判断一个区间能不能选,展开来主要有两点:

1、判断一个区间与之前已经选择的区间是否有冲突(交叉或者包含)

2、判断选择一个区间之后,剩下的区间可选数量能确保一共够k个

第一个问题:

可以借助set来对区间交叉以及包含的情况进行去重

定义区间结构体range以及set<range>

struct range
{
    int l, r;
    bool operator < (const range &x) const
    {
        return r < x.l;
    }
};

set<range> tr;

这样,当往set中insert区间range时,如果有交叉或者包含关系的range,会被视为相等从而起到去重的效果。

具体解释下为什么有交叉或包含关系的range在set中被视为相等:

  设两个区间x, y,

  交叉时:x.r > y.l ,  y.r > x.l,不满足operator <定义的顺序比较,因此视为相等。

  包含时:x.r > y.l, y.r > x.l,,不满足operator <定义的顺序比较,因此视为相等。

本题中,定义set<range> tr表示当前所有可用的空闲区间范围,一旦选择一个可用区间[x,y],需要在tr中查找到完全包含[x,y]的区间[l,r]

然后在tr中删除[l,r]区间,添加[l,x],[y,r]两个区间,这样就完成了区间的分割操作。

在set中查找是否存在包含一个区间可以用find,之后再进一步判断找到的区间是否完全包含而不是交叉。

第二个问题:

query(x, y)函数表示在[x,y]区间中可选区间最大数量

设初始区间是[start, end], 初始可选区间数量为all = query(start, end)

再进行若干次选择之后all会持续更新,更新方式如下:

当前选择了一个可用区间[x, y],在tr中找到的包含区间为[l,r],则剩余可选区间的数量变为all - query(l, r) + query(l, x) + query(y, r)

判断剩余可选区间数量 + 已选区间数量是否 >= k即可

因此,关键在于如何计算query(x, y),也就是[x, y]范围内可以最多选多少个区间?

贪心做法:

选一个当前区间[x, y],提前对所有区间按左端点排好序,下一个区间是枚举所有左端点大于等于y的区间,取一个右端点最小的,累计区间数量。

此方法,总的时间复杂度超过n^2,会超时。

32分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;

struct range
{
    int l, r;
    bool operator < (const range &x) const
    {
        return r < x.l;
    }
};
range a[N], b[N];
set<range> tr;
vector<int> ans;
int all; //剩余可选最大区间
int start = INT_MAX, ed = INT_MIN;
int n, k;

bool cmp(range x, range y)
{
    return x.l <= y.l;
}

int query(int l, int r)
{
    int res = 0, lastr = l;
    for(int i = 1; i <= n && b[i].l <= r; i++)
    {
        bool find = false;
        int minr = INT_MAX;
        for(int j = i; j <= n && b[j].l <= r; j++)
        {
            if(b[j].l >= lastr && b[j].r <= r)
            {
                find = true;
                if(b[j].r < minr)
                {
                    minr = b[j].r;
                    i = j;
                }
            }
        }
        lastr = minr;
        if(find) res++;
    }
    
    return res;
}

int main()
{
    cin >> n >> k;
    
    for(int i = 1; i <= n; i++) 
    {
        cin >> a[i].l >> a[i].r;
        start = min(start, a[i].l);
        ed = max(ed, a[i].r);
    }

    memcpy(b, a, sizeof(a));
    sort(b + 1, b + n + 1, cmp); //b按左端点排序

    tr.insert({start, ed}); //初始空闲区间范围
    all = query(start, ed);
    for(int i = 1; i <= n; i++)
    {
        range cur = a[i]; //当前区间
        int x = cur.l, y = cur.r;
        auto it = tr.lower_bound(cur);
        
        if(it != tr.end())
        {
            int l = (*it).l, r = (*it).r;
            if(l <= x && r >= y) //找到包含cur的区间
            {  
                if(all - query(l, r) + query(l, x) + query(y, r) >= k - ans.size() - 1)
                {
                    all = all - query(l, r) + query(l, x) + query(y, r);
                    ans.push_back(i);
                    tr.erase(it);
                    tr.insert({l, x});
                    tr.insert({y, r});
                }
            }

            if(ans.size() == k)
            {
                for(auto v : ans) cout << v << endl;
                return 0;
            }
        }
    }
    cout << -1;
    return 0;
}

倍增和ST表做法:

既然贪心枚举不行,需要优化,思考倍增方式,

设st[i][j]表示从i坐标开始,选择了2^j个区间之后,所能达到的最左的位置。

那么要计算query(x, y),

可以从大到小枚举j: log2(n) ~ 0,cur = x,如果st[cur][j] <= y,累加2^j,cur = st[cur][j]。

如何计算st[i][j]?

由于坐标取值范围达到10^9,首先需要对坐标进行离散化处理。

然后,借助st表的模型可以得到st[i][j] = st[st[i][j - 1]][j - 1]

初始时,

由于从所有区间左端点开始选择1个区间,一定到达其右端点,所以有

对于所有区间的[l, r],st[l][0] = min(st[l][0], r)

但是,除了区间的左端点,其他坐标如何初始化?

对于每一个坐标i都可以用右边一个坐标来更新st[i][0] = min(st[i][0], st[i+1][0]),注意i要从大到小遍历。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;

struct range
{
    int l, r;
    bool operator < (const range &x) const
    {
        return r < x.l;
    }
};
range a[N]; //所有活动
vector<int> c; //所有起始、结束时间
map<int, int> d; //离散化所用map

set<range> tr; //所有空闲的区间
int st[2 * N][20];
vector<int> ans;
int all; //剩余可选最大区间
int n, k;

void init()
{
    memset(st, 0x3f, sizeof(st));

    for(int i = 1; i <= n; i++)
    {
        st[a[i].l][0] = min(st[a[i].l][0], a[i].r); 
    }

    for(int i = c.size() - 1; i >= 0; i--)
    {
        st[i][0] = min(st[i][0], st[i + 1][0]);
    }

    for(int j = 1; j <= log2(n); j++)
    {
        for(int i = 0; i < c.size(); i++)
        {
            if(st[i][j - 1] < c.size())
                st[i][j] = st[st[i][j - 1]][j - 1];
        }
    }
}

int query(int l, int r)
{
    int res = 0, cur = l;
    for(int j = log2(n); j >= 0; j--)
    {
        if(st[cur][j] <= r)
        {
            res += (1 << j);
            cur = st[cur][j];
        }
    }
    
    return res;
}

int main()
{
    cin >> n >> k;
    for(int i = 1; i <= n; i++) 
    {
        cin >> a[i].l >> a[i].r;
        c.push_back(a[i].l);
        c.push_back(a[i].r);
    }

    //离散化
    sort(c.begin(), c.end());
    c.erase(unique(c.begin(), c.end()), c.end());
    for(int i = 0; i < c.size(); i++) d[c[i]] = i;
    for(int i = 1; i <= n; i++) a[i].l = d[a[i].l], a[i].r = d[a[i].r];

    init(); //初始化st表

    tr.insert({0, (int)c.size() - 1}); //初始空闲区间范围
    all = query(0, c.size() - 1); //剩余可选区间数

    for(int i = 1; i <= n; i++) //枚举所有区间
    {
        range cur = a[i]; //当前区间
        int x = cur.l, y = cur.r;
        auto it = tr.find(cur);
        
        if(it != tr.end())
        {
            int l = it->l, r = it->r;
            if(l <= x && r >= y) //找到包含cur的区间
            {
                if(all - query(l, r) + query(l, x) + query(y, r) >= k - ans.size() - 1) //选i区间后剩下的区间够满足k个
                {
                    //选择第i个区间
                    all = all - query(l, r) + query(l, x) + query(y, r);
                    ans.push_back(i);
                    tr.erase(it);
                    tr.insert({l, x});
                    tr.insert({y, r});
                }
            }

            if(ans.size() == k)
            {
                for(auto v : ans) cout << v << endl;
                return 0;
            }
        }
    }
    cout << -1;
    return 0;
}

 

posted @   五月江城  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示