【思维+树状数组_差分】Codeforces Round #595 (Div. 3)D. Too Many Segments

题意:

  • 给n个线段,我们需要保证每个点(只考虑整数点)的重复不超过k次,让我们找出要剔除的线段,并输出这些线段的位置。

D1. Too Many Segments (easy version)

思路:

没有思路,要说思路就是暴力!(哈哈)真的是纯暴力

  • 就是将一个线段分别以每个整数点为起点,右端点为终点,分为len个线段,都存起来。并且按照新的左端点升序,左端点相同线段长度降序排序。
  • 然后遍历一遍所有的线段,如果有不符合条件的就把这个子线段所在的原线段删掉【因为是按照长度降序排序的,所以贪心删掉最长的】。然后再存一下删掉线段的序号即可。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <stack>
#include <list>
#include <map>
 
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxN = 2e5 + 5;
 
struct node{
    int l, r, len, id;
    friend bool operator < (node n1, node n2)
    {
        if(n1.l == n2.l)
            return n1.len > n2.len;
        return n1.l < n2.l;
    }
}info[maxN];
int num[maxN] ,vis[maxN], ans[maxN];
void init()
{
    memset(num, 0, sizeof(num));
    memset(vis, 0, sizeof(vis));
    memset(ans, 0, sizeof(ans));
}
int main()
{
    int n, k;
    while(~scanf("%d%d", &n, &k))
    {
        int cnt = 0;
        init();
        for(int i = 1; i <= n; i ++ )
        {
            int a, b; scanf("%d%d", &a, &b);
            for(int j = a; j <= b; j ++ )
            {
                info[cnt ++ ] = node{j, b, b - j + 1, i};
                num[j] ++;
            }
        }
        sort(info, info + cnt);
        int pos = 0;
        for(int i = 0; i < cnt; i ++ )
        {
            if(vis[info[i].id])
                continue;
            if(num[info[i].l] > k)
            {
                for(int j = info[i].l; j <= info[i].r; j ++ )
                    num[j] -- ;
                vis[info[i].id] = 1;
                ans[pos ++ ] = info[i].id;
            }
        }
        sort(ans, ans + pos);
        printf("%d\n", pos);
        for(int i = 0; i < pos; i ++ )
            printf("%d%c", ans[i], " \n"[i == pos - 1]);
    }
    return 0;
}

D2. Too Many Segments (hard version)

思路

  • 记录下输入的最左和最右的端点,跑for( i = L, i <= R )循环,以 i 为右端点,将左端点在 i 之前的线段都 insert,然后我们从这些线段里剔除右端点最靠右的几个,使得该点的出现频率 == k.【这里用到了贪心的思想,越往右,覆盖的点越多】
  • 在剔除线段之前,我们需要将右端点小于 i 的线段踢出去,因为前边的已经满足出现频率在 k 之内了
  • 【维护整数点的频率】用到了树状数组的区间更新,单点查询。【差分思想】

差分_树状数组

  • D[maxN]: D[ i ]表示 A[ i ] - A[ i - 1 ]
  • 求A[ i ] = D[ i ] + D[ i - 1 ] + D[ i - 2 ] + … + D[ 1 ]
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <stack>
#include <list>
#include <map>
#define INF 0x3f3f3f3f
#define lowbit(x) x & (- x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxN = 2e5 + 5;

int n, k;
struct node{
    int l, r, id;
    node(int a = 0, int b = 0, int c = 0) : l(a), r(b), id(c) {}
    friend bool operator < (node n1, node n2) { return n1.r > n2.r ; }
}info[maxN];
bool cmp(node n1, node n2) { return n1.l < n2.l; }
int L, R;
int D[maxN];
void add(int x, int val)
{
    while(x <= R)
    {
        D[x] += val;
        x += lowbit(x);
    }
}
int getsum(int x)
{
    int ans = 0;
    while(x > 0)
    {
        ans += D[x];
        x -= lowbit(x);
    }
    return ans;
}

int main()
{
    scanf("%d%d", &n, &k);
    L = INF, R = 0;
    for(int i = 1; i <= n; i ++ )
    {
        scanf("%d%d", &info[i].l, &info[i].r);
        info[i].id = i;
        L = min(L, info[i].l);
        R = max(R, info[i].r);
    }
    for(int i = 1; i <= n; i ++ )
    {
        add(info[i].l, 1);
        add(info[i].r + 1, -1);
    }
    sort(info + 1, info + n + 1, cmp);
    multiset<node>st;
    set<int>ans;
    int pos = 1;
    for(int i = L; i <= R; i ++ )
    {
        if(getsum(i) <= k)
            continue;
        while(info[pos].l <= i && pos <= n)
        {
            st.insert(info[pos]);
            pos ++;
        }
        while(!st.empty() && (*st.rbegin()).r < i)
            st.erase(*st.rbegin());
        while(st.size() > k)
        {
            node tmp = *st.begin();
            ans.insert(tmp.id);
            st.erase(st.begin());
            add(tmp.l, -1);
            add(tmp.r + 1, 1);
        }
    }
    int cnt = ans.size();
    printf("%d\n", cnt);
    while(!ans.empty())
    {
        int tmp = *ans.begin();
        printf("%d ", tmp);
        ans.erase(tmp);
    }
    printf("\n");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值