Cocoicobird
热爱永远可以成为你继续下去的理由

牛客刷题-Day32

今日刷题:\(1066-1070\)

1066 NC13288 倒水

题目描述

有一个大水缸,里面水的温度为 \(T\) 单位,体积为 \(C\) 升。另有 \(n\) 杯水(假设每个杯子的容量是无限的),每杯水的温度为 \(t[i]\) 单位,体积为 \(c[i]\) 升。
现在要把大水缸的水倒入n杯水中,使得 \(n\) 杯水的温度相同,请问这可能吗?并求出可行的最高温度,保留 \(4\) 位小数。
注意:一杯温度为 \(t_1\) 单位、体积为 \(c_1\) 升的水与另一杯温度为 \(t_2\) 单位、体积为 \(c_2\) 升的水混合后,温度变为 \(\frac{t_1*c_1+t_2*c_2}{c_1+c_2}\),体积变为 \(c_1+c_2\)

输入描述

第一行一个整数 \(n, 1 ≤ n ≤ 10^5\)
第二行两个整数 \(T\)\(C\),其中 \(0 ≤ T ≤ 10^4, 0 ≤ C ≤ 10^9\)
接下来 \(n\) 行每行两个整数 \(t[i]\)\(c[i]\)\(0 < t[i], c[i] ≤ 10^4\)

输出描述

如果非法,输出 Impossible(不带引号)否则第一行输出 Possible(不带引号),第二行输出一个保留 \(4\) 位小数的实数表示答案。

示例

输入

3
10 2
20 1
25 1
30 1

输出

Possible
20.0000

样例解释:往第二杯水中倒 \(0.5\) 升水
往第三杯水中到 \(1\) 升水
三杯水的温度都变成了 \(20\)

解题思路

参考
假设温度相同,且水缸的水都用完,则有
\(\frac{t_1*c_1+T*c'_1}{c_1+c'_1}=\frac{t_2*c_2+T*c'_2}{c_2+c'_2}=...=\frac{t_n*c_n+T*c'_n}{c_n+c'_n}=ans=\frac{\sum_1^nt_i*c_i+T*C}{\sum_1^nc_i+C}\)

两种温度的水混合之后,温度会处于二者初始的温度之间。

  1. \(ans\le{min\{t_i\}}\),则水缸的水温较低,可以使用一些水缸的水,使得所有水杯的水的温度降低到 \(min\{t_i\}\)
  2. \(ans\ge{max\{t_i\}}\),则水缸的水温较高,可以使用一些水缸的水,使得所有水杯的水的温度升高到 \(max\{t_i\}\)
  3. \({min\{t_i\}}<ans<{max\{t_i\}}\):若 \(T=ans\),则使用一些水缸的水,所有水杯的温度不可能达到 \(T\);若 \(T≠ans\),则使用一些水缸的水,部分水杯的温度会跨越 \(T\),也不可能。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;

double T, C;
int n;
struct Cup {
    double t, c;
} a[N];
// NC13288
int main() {
    scanf("%d%lf%lf", &n, &T, &C);
    for (int i = 1; i <= n; i++)
        scanf("%lf%lf", &a[i].t, &a[i].c);
    double maxt = 0, mint = 1e5;
    double sum1 = T * C, sum2 = C;
    for (int i = 1; i <= n; i++) {
        maxt = max(maxt, a[i].t);
        mint = min(mint, a[i].t);
        sum1 += a[i].t * a[i].c;
        sum2 += a[i].c;
    }
    double ans = sum1 / sum2;
    if (ans <= mint) {
        printf("Possible\n%.4lf", mint);
    } else if (ans >= maxt) {
        printf("Possible\n%.4lf", ans);
    } else {
        printf("Impossible\n");
    }
}

1067 NC16640 [NOIP2007]纪念品分组

题目描述

元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得 的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品, 并且每组纪念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。
你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。

输入描述

\(1\) 行包括一个整数 \(w\),为每组纪念品价格之和的上限。
\(2\) 行为一个整数 \(n\),表示购来的纪念品的总件数。
\(3 ~ n+2\) 行每行包含一个正整数 \(p_i ( 5 ≤ p_i ≤ w )\),表示所对应纪念品的价格。

输出描述

包含一个整数,即最少的分组数目。

示例

输入

100
9
90
20
20
30
50
60
70
80
90

输出

6
备注

\(50\%\) 的数据满足:\(1 ≤ n ≤ 15\)
\(100\%\) 的数据满足:\(1 ≤ n ≤ 30000, 80 ≤ w ≤ 200\)

解题思路

由于每组最多只能包括两件纪念品,所以按照价格从低到高排序,高价的纪念品尽量和低价的去配对,无法配对就单独分组。

C++ 代码
// NC16640
#include <bits/stdc++.h>
using namespace std;
const int N = 30010;

int n, w;
int p[N];

int main() {
    scanf("%d%d", &w, &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &p[i]);
    sort(p + 1, p + n + 1);
    int ans = 0;
    int l, r;
    for (l = 1, r = n; l < r; ) {
        if (p[l] + p[r] <= w)
            l++, r--;
        else {
            r--;
        }
        ans++;
    }
    if (l == r)
        ans++;
    printf("%d\n", ans);
    return 0;
}

1068 NC23036 华华听月月唱歌

题目描述

月月唱歌超级好听的说!华华听说月月在某个网站发布了自己唱的歌曲,于是把完整的歌曲下载到了U盘里。然而华华不小心把U盘摔了一下,里面的文件摔碎了。月月的歌曲可以看成由1到N的正整数依次排列构成的序列,它现在变成了若干个区间,这些区间可能互相重叠。华华想把它修复为完整的歌曲,也就是找到若干个片段,使他们的并集包含 \(1\)\(N\)(注意,本题中我们只关注整数,见样例1)。但是华华很懒,所以他想选择最少的区间。请你算出华华最少选择多少个区间。因为华华的U盘受损严重,所以有可能做不到,如果做不到请输出 \(-1\)

输入描述

第一行两个正整数 \(N\)\(M\),表示歌曲的原长和片段的个数。
接下来 \(M\) 行,每行两个正整数 \(L\)\(R\) 表示第 \(i\) 的片段对应的区间是 \([L,R]\)

输出描述

如果可以做到,输出最少需要的片段的数量,否则输出 \(-1\)

示例1

输入

4 2
1 2
3 4

输出

2
示例2

输入

4 2
1 1
3 4

输出

-1
示例3

输入

10 5
1 1
2 5
3 6
4 9
8 10

输出

4
备注

\(1≤L≤R≤10^9,1≤N≤10^9,1≤M≤10^5\)

解题思路

做不到的情况就是最终拼接之后覆盖不了 \([1,N]\)
按照左端点排序,在选取下一个片段时,尽量选择其左端点在已覆盖区间内,且右端点最大的片段。

C++ 代码
// NC23036
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;

int n, m;
struct Song {
    int l, r;
} a[N];

bool cmp(Song a, Song b) {
    if (a.l == b.l)
        return a.r > b.r;
    return a.l < b.l;
}

int main() {
    scanf("%d%d", &m, &n);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &a[i].l, &a[i].r);
    sort(a + 1, a + n + 1, cmp);
    int ans = 0, last = 0, i = 0, r = 0;
    while (last < m) { // last 为上次扩展的右端点
        while (i <= n && a[i].l <= last + 1) { // 
            r = max(r, a[i].r);
            i++;
        }
        if (r > last) {
            last = r;
            ans++;
        } else
            break;
    }
    if (last == m)
        printf("%d\n", ans);
    else
        puts("-1");
    return 0;
}

1069 NC200183 牛妹和01串

题目描述

牛妹有一个 \(01\) 串,串中只包含 \(0\)\(1\),牛妹要把这个串划分成连续的 \(m\) 段,使得每一段至少包含一个 \(0\) 和一个 \(1\)
牛妹想最大化 \(m\)\(m\) 最大是多少呢?

输入描述

输入包含一行一个 \(01\)\(S\)。保证中至少包含一个 \(0\) 和一个 \(1\)

输出描述

输出一行一个整数表示答案。

示例

输入

10101111000010101111010101

输出

9
备注

\(2≤∣S∣≤1e^5\)\(∣S∣\) 表示字符串 \(S\) 的长度。

解题思路

当目前连续序列中存在至少一个 \(0\)\(1\) 就分割。
证明:假设按照贪心的方式划分出的序列为 \(T=\{s_1,s_2,...,s_t\}\),而最优解划分出的序列为 \(K=\{o_1,o_2,...,o_k\}\)。则有 \(|s_1|\le|o_1|\),那么通过贪心方式划分出第一段之后的剩余序列长度不小于最优解划分出第一段之后的剩余序列长度,那么这两个剩余序列继续划分,在贪心的序列中划分的段数必然不小于划分最优解序列的段数。如此重复,每次贪心划分剩下的序列长度都要更长。

C++ 代码
// NC200183
#include <bits/stdc++.h>
using namespace std;

string S;

int main() {
    cin >> S;
    int ans = 0;
    for (int i = 0; S[i]; i++) {
        int j = i;
        int a = 0, b = 0; // 表示 0 和 1 是否包含
        while (S[j] && (!a || !b)) {
            if (S[j] == '0') a++;
            else b++;
            j++;
        }
        if (a && b)
            ans++;
        i = j - 1;
    }
    printf("%d\n", ans);
    return 0;
}

1070 NC25136 [USACO 2006 Ope B]Cows on a Leash

题目描述

给定如图所示的若干个长条。你可以在某一行的任意两个数之间作一条竖线,从而把这个长条切开,并可能切开其他长条。问至少要切几刀才能把每一根长条都切开。样例如图需要切两刀。
image

注意:输入文件每行的第一个数表示开始的位置,而第二个数表示长度。

输入描述

Line 1: A single integer, \(N(2 <= N <= 32000)\)
Lines 2..\(N+1\): Each line contains two space-separated positive integers that describe a leash. The first is the location of the leash's stake; the second is the length of the leash.(\(1 <= length <= 1e7\))

输出描述

Line 1: A single integer that is the minimum number of cuts so that each leash is cut at least once.

示例

输入

7
2 4
4 7
3 3
5 3
9 4
1 5
7 3

输出

2
解题思路

每根长条对应一个区间:\([l_i, r_i], \quad r_i = l_i + len_i-1\)
一次切刀 = 在某个位置 \(x\) 竖着切:\(l_i < x < r_i\),则区间 \(i\) 被切开。
目标:用最少的点覆盖所有区间。

  1. 按右端点从小到大排序
  2. 每次选当前区间的右端点作为切点

证明:假设贪心用了 \(k\) 刀,最优方案用了 \(m\) 刀。需要证明 \(k = m\)

把区间按右端点排序:\(r_1 \le r_2 \le \cdots \le r_n\)。设贪心第一个切点 \(x_1 = r_1\),某个最优解的切点集合 \(O = \{y_1, y_2, \dots, y_m\}\),其中 \(y_1 < y_2 < \cdots < y_m\)

引理:存在一个最优解,使它的第一个切点等于贪心切点 \(x_1\)
因为:第一个区间是 \([l_1, r_1]\),它必须被切。
所以:任何合法解,都必须选一个点:\(y_1 \in (l_1, r_1)\)
而贪心选:\(x_1 = r_1-1\)显然:\(y_1 \le r_1-1= x_1\)(因为超过 \(r_1\) 就切不到第一个区间)

把最优解中的第一个切点换掉:\(O' = \{x_1, y_2, \dots, y_m\}\)
也就是把 \(y_1\) 换成 \(x_1\)

为什么 O' 仍然合法?
需证明:原来被 \(y_1\) 切到的区间,现在也能被 \(x_1\) 切到。
考虑任意被 \(y_1\) 覆盖的区间 \([l, r]\)\(l < y_1 < r\)
又因为:\(y_1 \le x_1 = r_1-1\)
并且所有区间满足:\(r \ge r_1 > x_1\)
(否则它会排在第一个前面)
所以:\(l < y_1 \le x_1 < r\)\(x_1\) 仍然在区间内部,所以不会少覆盖任何区间

现在已经让最优解第一个点 = 贪心第一个点。删除所有被 \(x_1\) 覆盖的区间。剩下问题变成:在剩余区间中,最少选点覆盖。
这是一个规模更小的同类问题

假设规模为 \(n−1\) 时,贪心最优。
那么规模为 \(n\) 时:

  • 第一步安全
  • 后续最优

所以整体最优。

C++ 代码
// NC25136 [USACO 2006 Ope B]Cows on a Leash
#include <bits/stdc++.h>
using namespace std;
const int N = 32010;

int n;
pair<int, int> a[N];

bool cmp(pair<int, int> a, pair<int, int> b) {
    if (a.second == b.second)
        return a.first < b.first;
    return a.second < b.second;
}

int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        int l, len;
        scanf("%d%d", &l, &len);
        a[i] = {l, l + len};
    }
    sort(a, a + n, cmp);
    int ans = 0, last = 0;
    for (int i = 0; i < n; i++) {
        if (last <= a[i].first) {
            last = a[i].second;
            ans++;
        }
    }
    printf("%d\n", ans);
    return 0;
}
posted on 2026-03-03 15:43  Cocoicobird  阅读(1)  评论(0)    收藏  举报