Codeforces Round 927 (Div. 3) F. Feed Cats【差分+排序+dp】

原题链接:https://codeforces.com/problemset/problem/1932/F

题目描述

你在玩一个游戏,这个游戏有 n 步。你有 m 只猫,每只猫有特定的饲养时间 [li​,ri​]。如果你在第 x 步决定饲养,那么所有满足 li​≤x≤ri​ 的猫都会被饲养;或者你不决定饲养,那么无事发生。但是如果一只猫被饲养了两次及以上,它就会死亡。请问在没有猫死亡的情况下,最多有多少只猫被饲养了至少一次?

输入格式

第一行一个整数 t,表示数据组数。每组数据格式如下:

第一行两个整数 n,m,含义如上。

接下来 m 行每行两个整数 li​,ri​,含义如上。

1≤t≤10^4,1≤n,∑n≤10^6,1≤m,∑m≤2×10^5,1≤li​≤ri​≤n。

输出格式

每组数据一行一个整数表示在满足条件的情况下最多有多少只猫被饲养了至少一次。

输入输出样例
输入
3
15 6
2 10
3 5
2 4
7 7
8 12
11 11
1000 1
1 1000
5 10
1 2
3 4
3 4
3 4
3 4
1 1
1 2
3 3
3 4
3 4
输出
5
1
10

说明/提示

In the first example, one of the ways to feed five cats is to feed at steps 44 and 1111 .

  • At step 4 , cats 1 , 2 , and 3 will be fed.
  • At step 11 , cats 5 and 6 will be fed.

解题思路:

第i条线段表示第i只猫可以被喂养的位置,每只猫最多被喂养一次也就是说每个线段区间中最多选择一个点,然后如果选择了第i个位置,就必须要选择出现在第i个位置的所有猫进行喂养,需要我们求在满足上述条件的基础上最多喂养多少只猫,现在假设我们选择喂养第i个位置上的所有猫,那么我们需要找到包含位置i的所有线段区间,由于这些线段中的其他点不能在选择,也就是从当前位置往前一直到这些线段中左端点的最小位置上的所有其他点都不能再选择,这个题目n很大,所以我们肯定不能暴力的去找这个位置,为了方便找这个位置,我们可以先对所有区间按照左端点从小到大排序即可,排序之后就很明显可以看出可以进行dp处理了。

定义f(i,1/0)表示是否投喂第i个位置能喂养的最多猫的个数,那么就有如下状态转移

f(i,0)=max(f(i-1,0),f(i-1,1))

f(i,1)=max(f(lp[i]-1,0),f(lp[i]-1,1))+cnt,然后max(f(lp[i]-1,0),f(lp[i]-1,1))是等价于f(lp[i],0)的

所以可以得出如下状态转移方程:

f(i,0)=max(f(i-1,0),f(i-1,1))

f(i,1)=f(lp[i],0)+cnt)

其中lp[i]表示覆盖位置 i 的所有线段区间的左端点的最小值,如果没有区间覆盖 i ,那么lp[i]=i,cnt表示覆盖位置i的线段区间数,其中lp通过排序后可以使用双指针预处理好,cnt可以差分预处理。

时间复杂度:O(n+m)。

空间复杂度:O(n+m)。

cpp代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int N = 1e6 + 10, M = 2e5 + 10, mod = 1e9 + 7;

int T, n, m;
struct Cat
{
    int l, r;
} cat[M];
int f[N][2], diff[N], lp[N];

void solve()
{
    cin >> n >> m;
    for (int i = 1; i <= m; i++)
        cin >> cat[i].l >> cat[i].r;
    sort(cat + 1, cat + 1 + m, [&](Cat cat1, Cat cat2) { // 对于所有线段区间按照左端点从小到大排序
        return cat1.l < cat2.l;
    });
    for (int i = 0; i <= n; i++)
        f[i][0] = f[i][1] = diff[i] = 0; // 多测,记得清空全局数组

    for (int i = 1; i <= m; i++) // 差分预处理cnt
    {
        diff[cat[i].l]++;
        diff[cat[i].r + 1]--;
    }
    int j = 1;
    for (int i = 1; i <= n; i++) // 双指针预处理lp[i]
    {
        if (i < cat[j].l)
            lp[i] = i;
        else if (cat[j].l <= i && i <= cat[j].r)
            lp[i] = cat[j].l;
        else
        {
            while (j <= m && cat[j].r < i)
                j++;
            if (j > m || i < cat[j].l)
                lp[i] = i;
            else
                lp[i] = cat[j].l;
        }
    }

    int cnt = 0;
    // dp
    for (int i = 1; i <= n; i++)
    {
        cnt += diff[i];
        f[i][0] = max(f[i - 1][0], f[i - 1][1]); // 不选择第i个位置
        f[i][1] = f[lp[i]][0] + cnt;             // 选择第i个位置
    }
    cout << max(f[n][0], f[n][1]) << '\n';
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值