Too Many Segments (hard version) CodeForces - 1249D2(贪心+容器vector+set)

题目

给多组线段,而每一个点的覆盖次数不超过K,每次可去除一个线段,问最少去多少线段以及线段的位置。
The only difference between easy and hard versions is constraints.

You are given nn segments on the coordinate axis OX. Segments can intersect, lie inside each other and even coincide. The ii-th segment is [li;ri] (li≤ri) and it covers all integer points j such that li≤j≤ri.

The integer point is called bad if it is covered by strictly more than kk segments.

Your task is to remove the minimum number of segments so that there are no bad points at all.

Input
The first line of the input contains two integers nn and kk (1≤k≤n≤2⋅10 5 ^{5} 5) — the number of segments and the maximum number of segments by which each integer point can be covered.

The next nn lines contain segments. The ii-th line contains two integers lili and riri (1≤li≤ri≤2⋅10 5 ^{5} 5) — the endpoints of the ii-th segment.

Output
In the first line print one integer mm (0≤m≤n) — the minimum number of segments you need to remove so that there are no bad points.

In the second line print mm distinct integers p1,p2,…,pm(1≤pi≤n) — indices of segments you remove in any order. If there are multiple answers, you can print any of them.

Examples
Input
7 2
11 11
9 11
7 8
8 9
7 8
9 11
7 9
Output
3
4 6 7
Input
5 1
29 30
30 30
29 29
28 30
30 30
Output
3
1 4 5
Input
6 1
2 3
3 3
2 3
2 2
2 3
2 3
Output
4
1 3 5 6
官方题解:
In this problem, we need to implement the same greedy solution as in the easy version, but faster. Firstly, let’s calculate for each point the number of segments covering it. We can do it using standard trick with prefix sums: increase cntli, decrease cntri+1 and build prefix sums on the array cnt.

Let’s maintain the set of segments that cover the current point, sorted by the right endpoint. We can do this with almost the same trick: append to the array evli the index i that says us that in the point li the i-th segment is opened. And add to the evri+1 the index −i that says us that in the point ri+1 the i-th segment is closed. Note that you need to add 11-indexed values i because +0 and −0 are the same thing actually. We can change the array cntcnt to carry the number of segments covering each point using some structure, but we don’t need to do it. Let’s maintain the variable curSub that will say us the number of segments covering the current point that was already removed. Also, let’s carry another one array sub which will say us when we need to decrease the variable curSub.

So, we calculated the array of arrays ev, the array cnt and we can solve the problem now. For the point i, let’s remove and add all segments we need, using the array evievi and add subi to curSub. Now we know that the set of segments is valid, curSub is also valid and we can fix the current point if needed. While cnti−curSub>k, let’s repeat the following sequence of operations: take the segment with the maximum right border rmaxrmax from the set, remove it, increase curSub by one and decrease subrmax+1by one.

Note that when we remove segments from the set at the beginning of the sequence of moves for the point ii, we don’t need to remove segments that we removed by fixing some previous points, and we need to pay attention to it.

Time complexity: O(nlogn).

百度翻译:

在这个问题上,我们需要实现与简单版本相同的贪婪解决方案,但速度更快。首先,让我们计算每个点覆盖它的段数我们可以使用带前缀和的标准技巧:增加cntli,减少cntri+1,并在阵列cnt上构建前缀和。
让我们保持覆盖当前点的线段集,按右端点排序我们可以使用几乎相同的技巧来实现这一点:将索引i附加到数组evli中,该索引i表示在点li中,第i段已打开再加上evri+1的指数-i,表示在ri+1点,第i段是闭合的注意,您需要添加11个索引值i,因为+0和-0实际上是相同的我们可以使用某种结构来改变阵列碳纳米管来携带覆盖每个点的段数,但我们不需要这样做。让我们维护一个变量cursub,它将告诉我们覆盖当前点的段数,这个点已经被删除了。另外,让我们携带另一个数组sub,当我们需要减少变量cursub时,它会告诉我们。
因此,我们计算了阵列的ev,阵列的cnt,我们现在可以解决这个问题对于点i,让我们使用数组eviev移除并添加我们需要的所有段,并将subi添加到curSub现在我们知道段集是有效的,curSub也是有效的,如果需要,我们可以修复当前点在重新编译时,让我们重复以下操作序列:从集合中选择最大右边界的段,删除它,将其增加一个,减少一个。
请注意,当我们在第二点的动作序列开始时从集合中移除片段时,我们不需要移除通过修复某些先前点而移除的片段,并且我们需要注意它。
时间复杂度:O(nLogn)。

思路

贪心思想,1. 以区间左端点为头,右端点由小到大排序,如果端点相同,按标号存入。
2.按排好的线段顺序,从头开始遍历每个点,判断每个点上覆盖次数,大于k时删除最长的那个区间。
3.并且真正有用的覆盖点其实就是端点(其余点都可以等价到端点上),,把每个右端点带上标号放进set(记录所有以x为左端点的线段)。

AC代码

#include<cstdio>
#include<cstring>
#include<vector>
#include<set>
#include<algorithm>
using namespace std;
const int N=2*1e5+10;
struct node
{
    int id;
    int r;
    bool operator <( const node &v)const
    {
        if(r!=v.r)
            return r<v.r;/*右端点从小到大排序*/
        else
            return id<v.id;/*下标从小到大排序*/
    }
};
vector<node>v[N];/*vector容器里面存放的是结构体,开了一个v[N]的数组,相当于二维数组*/
vector<int>dp;
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1; i<=n; i++)
    {
        node q;
        int x,y;
        scanf("%d%d",&x,&y);
        q.id=i;
        q.r=y;
        v[x].push_back(q);/*记录所有以x为左端点的线段*/
    }
    set<node>s;
    for(int i=1; i<N; i++)
    {
        while(s.size()&&(*s.begin()).r<i)/*刚开始是不执行while循环的,若set容器中存的线段的右端点小于下一个点,那么这条边就可以删去*/
            s.erase(*s.begin());
        for(int j=0; j<v[i].size(); j++) /*遍历以顶点i为左端点的所有的线段*/
            s.insert(v[i][j]);/*set容器中存的是这条线段的所有信息,set容器插入元素时,会自动调整二叉树的结构,此时的‘<’的重载操作就起作用*/
        while(s.size()>k)
        {
            dp.push_back((*s.rbegin()).id);
            s.erase(*s.rbegin());
        }
    }
    printf("%d\n",dp.size());
    for(int i=0; i<dp.size(); i++)
        printf("%d ",dp[i]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值