8.12信息学集训_摸底+贪心

摸底赛题

P9955 [USACO20DEC] Do You Know Your ABCs? B

有三个正整数 \(A\)\(B\)\(C\)\(A\le B\le C\))。这些数字范围在 \(1\ldots 10^9\) 之间的整数(不一定各不相同),并且是 \(A\)\(B\)\(C\)\(A+B\)\(B+C\)\(C+A\)\(A+B+C\) 的某种排列。

给定这七个整数,求出 \(A\)\(B\)\(C\)。可以证明,答案是唯一的。

输入格式:输入一行,包含七个空格分隔的整数。
输出格式:输出 \(A\)\(B\)\(C\),用空格分隔。

in:
2 2 11 4 9 7 9
out:
2 2 7

【分析】7个数字中选3个数,匹配要求,推导一下有如下信息

\[\begin{align*} &A\le B \le C \\ &A \le B\le C, A+B \le C+ A \le B+C \le A+B+C\\ &C = A+B+C - A-B \quad \text{(^_^)} \end{align*} \]

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, INF = 0x3f3f3f3f, MOD = 1E9 + 7;

int main(int argc, char* argv[]) {
    int a[10], n = 7;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + 1 + n);
    a[3] = a[7] - a[1] - a[2];
    cout << a[1] << " " << a[2] << " " << a[3];
    return 0;
}

P5436 【XR-2】缘分

一禅和师父约定一个正整数 \(n\),接着他们各自在心里想一个不超过 \(n\) 的正整数,这两个数的最小公倍数最大会是多少。

输入格式:
多组数据,第一行一个正整数 \(T\),表示数据组数。
接下来的 \(T\) 行,每行一个正整数 \(n\),表示一禅和师父约定的正整数。

输出格式:对每组数据,一行一个正整数,表示答案。

in:
1
3
out:
6

【样例说明】不超过 \(3\) 的两个正整数的最小公倍数的最大值为 \(\mathrm{lcm}(2,3) = 6\)

【数据规模与约定】

\(50\%\) 的数据,\(1 \le T,n \le 100\)

\(100\%\) 的数据,\(1 \le T \le 100, 1 \le n \le 10^9\)

【分析】\(a,b\le n,\quad ans = max(lcm(a,b))\)

方法1:枚举 \(a,b\),复杂度 \(O(Tn^2)\),得分 50;

方法2:推导一下,\(lcm(a,b) = a*b/gcd(a,b)\),想 \(lcm\) 越大,那么 \(a*b\) 越大,\(gcd(a,b)\) 越小
\(gcd(n-1, n)=1\),所以 \(a=n-1, b=n\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
typedef long long ll;
ll gcd(ll a, ll b) {
    return b ? gcd(b, a % b) : a;
}
ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}
// 求 max(lcm(a,b)) = max(a*b/gcd(a,b)),则a b互质就是答案
int main() {
    ll t, n;
    cin >> t;
    while (t--) {
        cin >> n;
        cout << (n == 1 ? 1 : n * (n - 1)) << endl;
    }
    return 0;
}

P1182 数列分段 Section II

对于给定的一个长度为 \(N\) 的正整数数列 \(A_{1\sim N}\),现要将其分成 \(M\)\(M\leq N\))段,并要求每段连续,且每段和的最大值最小。

in:
5 3
4 2 4 5 1
out:
6

【数据规模与约定】

对于 \(20\%\) 的数据,\(N\leq 10\)
对于 \(40\%\) 的数据,\(N\leq 1000\)
对于 \(100\%\) 的数据,\(1\leq N\leq 10^5\)\(M\leq N\)\(A_i < 10^8\), 答案不超过 \(10^9\)

【分析】最大值最小,典型二分答案

二分左边界 x, chk 当最大值 x 的时候是否合法
思考 是否需要 long long

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n, m, a[N];
// 最大值 x 的情况下能否分出至多 m 段
bool chk(int x) {
    int s = 0, t = 0;
    for (int i = 1; i <= n; i++) {
        if (t + a[i] < x) t += a[i];
        else if (t + a[i] == x) t = 0, s++;
        else t = a[i], s++;
    }
    if (t) s++;
    return s <= m;
}
int main() {
    int l = 1, r = 1e9, ans = r;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i], l = max(l, a[i]);
    while (l <= r) {
        int mid = l + r >> 1;
        chk(mid) ? r = mid - 1, ans = mid : l = mid + 1;
    }
    cout << ans << endl;
    return 0;
}

P1032 [NOIP2002 提高组] 字串变换

已知有两个字串 \(A,B\) 及一组字串变换的规则(至多 \(6\) 个规则),形如:

  • \(A_1\to B_1\)
  • \(A_2\to B_2\)

规则的含义为:在 \(A\) 中的子串 \(A_1\) 可以变换为 $ B_1\(,\)A_2$ 可以变换为 \(B_2\cdots\)

输入格式:第一行有两个字符串 \(A,B\)。接下来若干行,每行有两个字符串 \(A_i,B_i\),表示一条变换规则。

输出格式:若在 \(10\) 步(包含 \(10\) 步)以内能将 \(A\) 变换为 \(B\),则输出最少的变换步数;否则输出 NO ANSWER!

in:
abcd xyz
abc xu
ud y
y yz
out:
3

【数据规模与约定】
对于 \(100\%\) 数据,保证所有字符串长度的上限为 \(20\)

【分析】最小步数,一眼bfs

题目主要在数据的处理上面有点难度,现在 x,y 是字符串类型,不好记录 g[x][y]=1 的情况。

思考一下,可以使用 map<pair<string,string>, bool> mp;

问题解决, bfs 即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
string A, B, a, b;
map<pair<string, string>, bool> mp;
map<string, int> st;

void bfs() {
    queue<string> q;
    q.push(A), st[A] = 1;
    while (q.size()) {
        auto u = q.front(); q.pop();
        if (st[u] > 10) continue;
        if (u == B) {
            cout << st[u] - 1 << endl;
            return;
        }
        for (auto it : mp) {
            a = it.first.first, b = it.first.second;
            int p = u.find(a);
            while (p != -1) {
                string v = u;
                v.replace(v.begin() + p, v.begin() + p + a.size(), b);
                if (!st[v]) st[v] = st[u] + 1, q.push(v);
                p = u.find(a, p + a.size());
            }
        }
    }
    cout << "NO ANSWER!" << endl;
}
int main() {
    cin >> A >> B;
    while (cin >> a >> b) mp.insert({make_pair(a, b), 1});
    bfs();
    return 0;
}

P1020 [NOIP1999 提高组] 导弹拦截

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式:一行,若干个整数,中间由空格隔开。

输出格式:两行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

in:
389 207 155 300 299 170 158 65
out:
6
2

【数据规模与约定】
对于前 \(50\%\) 数据(NOIP 原题数据),满足导弹的个数不超过 \(10^4\) 个。该部分数据总分共 \(100\) 分。可使用\(\mathcal O(n^2)\) 做法通过。
对于后 \(50\%\) 的数据,满足导弹的个数不超过 \(10^5\) 个。该部分数据总分也为 \(100\) 分。请使用 \(\mathcal O(n\log n)\) 做法通过。

对于全部数据,满足导弹的高度为正整数,且不超过 \(5\times 10^4\)

【分析】题目含有两个问题

// v1 : 最长不上升子序列长度
// v2 : 需要多少导弹

解法1:DP,复杂度 \(O(n^2)\)

解法2:贪心+二分,复杂度 \(O(nlogn)\)

具体内容不在赘述,可查阅

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
int n = 0, a[N], f[N], h[N];
// v1 : 最长下降子序列长度
// v2 : 需要多少导弹
namespace dp {
void solve() {
    int ans1 = 0, ans2 = 0;
    for (int i = 1; i <= n; i++) {
        f[i] = 1;
        for (int j = 1; j < i; j++)
            if (a[i] <= a[j]) f[i] = max(f[i], f[j] + 1);
        ans1 = max(ans1, f[i]);
    }
    for (int i = 1; i <= n; i++) {
        int k = 0;
        while (k <= ans2 && h[k] < a[i]) k++;
        h[k] = a[i];
        if (k > ans2) ans2++;
    }
    cout << ans1 << endl << ans2 << endl;
}

};  // namespace dp
namespace binary_Search {
void solve() {
    int ans1 = 0, ans2 = 0;
    for (int i = n; i >= 1; i--) {
        if (i == n || f[ans1 - 1] <= a[i]) f[ans1++] = a[i];
        else *upper_bound(f, f + ans1, a[i]) = a[i];
    }
    for (int i = 1; i <= n; i++) {
        int k = lower_bound(h + 1, h + 1 + ans2, a[i]) - h;
        h[k] = a[i];
        if (k > ans2) ans2++;
    }
    cout << ans1 << endl << ans2 << endl;
}
};  // namespace binary_Search
int main() {
    while (cin >> a[++n]); n--;
    // dp::solve();
    binary_Search::solve();
    return 0;
}

P1077 [NOIP2012 普及组] 摆花

小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共 \(m\) 盆。通过调查顾客的喜好,小明列出了顾客最喜欢的 \(n\) 种花,从 \(1\)\(n\) 标号。为了在门口展出更多种花,规定第 \(i\) 种花不能超过 \(a_i\) 盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。

试编程计算,一共有多少种不同的摆花方案。

输入格式:第一行包含两个正整数 \(n\)\(m\),中间用一个空格隔开。第二行有 \(n\) 个整数,每两个整数之间用一个空格隔开,依次表示 \(a_1,a_2, \cdots ,a_n\)

输出格式:一个整数,表示有多少种方案。注意:因为方案数可能很多,请输出方案数对 \(10^6+7\) 取模的结果。

in:
2 4
3 2
out:
2

【数据范围】
对于 \(20\%\) 数据,有 \(0<n \le 8,0<m \le 8,0 \le a_i \le 8\)
对于 \(50\%\) 数据,有 \(0<n \le 20,0<m \le 20,0 \le a_i \le 20\)
对于 \(100\%\) 数据,有 \(0<n \le 100,0<m \le 100,0 \le a_i \le 100\)

【分析】

好好阅读理解这句话的含义:摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
也就意味着,不需要考虑花的位置,只需要考虑数量;那么问题就很显然,这是一个多重背包的裸题。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int n, m, a[N], mod = 1e6 + 7;

namespace DP1 {
int dp[N][N];  // dp[i][j] 用前 i 种花摆 j 盆的方案数
void solve() {
    for (int i = 0; i <= n; i++)
        dp[i][0] = 1;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            for (int k = 0; k <= a[i] && k <= j; k++) {
                int& t = dp[i][j];
                t = (t + dp[i - 1][j - k]) % mod;
            }
    cout << dp[n][m] << endl;
}
};  // namespace DP1

namespace DP2 {
int dp[N];  // dp[j] 摆 j 盆的方案数
void solve() {
    dp[0] = 1;
    for (int i = 1; i <= n; i++)
        for (int j = m; j >= 1; j--)
            for (int k = 1; k <= a[i] && k <= j; k++) {
                int& t = dp[j];
                t = (t + dp[j - k]) % mod;
            }
    cout << dp[m] << endl;
}
};  // namespace DP2

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    DP1::solve();
    DP2::solve();
    return 0;
}

T264125 黑暗能量

\(n\) 瓶装有黑暗能量的容器,第 \(i\) 个容器里装有质量为 \(a_i\) 的黑暗能量。
两人尽可能多的平分黑暗能量,剩下无法平分的黑暗能量只能放弃使用。

【数据规模与约定】
本题共有 \(20\) 个测试点。
对于 \(50\%\) 的数据,\(n \le 13\)
对于 \(70\%\) 的数据,\(n \le 50\),提供的黑暗能量的总质量不超过 \(10^3\)
对于 \(100\%\) 的数据,\(n \le 500\),提供的黑暗能量的总质量不超过 \(10^5\)

注意:对于以上三档数据,每档数据中分别含有 \(1\) 个测试点空间限制为 \(2\) MB,即总共有 \(15\%\) 的测试点空间限制为 \(2\) MB,其余测试点空间限制为 \(256\) MB。

【分析】
对于 \(50\%\) 的数据,\(n \le 13\);--- 枚举所有情况 \(3^{13}\),很小
满分解法,DP + 滚动数组优化,具体描述看代码

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 501, INF = 0x3f3f3f3f;
int n, m, a[N];

// 枚举 TLE:3^n ---- 012
void solve1() {
    int ans = 0, tt = pow(3, n);
    for (int i = 0; i < tt; i++) {
        int t = i, p = n, b[3] = {0};
        while (t) {
            b[t % 3] += a[p--], t /= 3;
        }
        if (b[1] == b[2])
            ans = max(ans, b[1]);
    }
    cout << 2 * ans << endl;
}

// dp[i][j][k] -- 前 i件物品,羊j 狼k 的情况是否存在
void solve2() {
    bool dp[501][501][501];
    memset(dp, 0x00, sizeof dp);
    dp[0][0][0] = 1;
    int p = 500;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= p; j++) {
            for (int k = 0; k <= p; k++) {
                bool& t = dp[i][j][k];
                t = dp[i - 1][j][k];
                if (!t && j >= a[i])
                    t = dp[i - 1][j - a[i]][k];
                if (!t && k >= a[i])
                    t = dp[i - 1][j][k - a[i]];
            }
        }
    }
    int ans = 0;
    for (int i = 0; i <= p; i++)
        ans = max(ans, dp[n][i][i] ? i : 0);
    cout << 2 * ans << endl;
}

// dp[i][j] -- 前 i件物品,羊狼差距 j 的最大质量
void solve3() {
    int dp[501][50001];
    memset(dp, 0x80, sizeof dp);
    dp[0][0] = 0;
    int p = 50000;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= p; j++) {
            int& t = dp[i][j];
            t = dp[i - 1][j];
            if (j + a[i] <= p)
                t = max(t, dp[i - 1][j + a[i]] + a[i]);
            t = max(t, dp[i - 1][abs(j - a[i])] + a[i]);
        }
    }
    cout << dp[n][0] << endl;
}

// dp[i][j] -- 前 i件物品,羊狼差距 j 的最大质量
void solve4() {
    int dp[2][100001];
    memset(dp, 0x80, sizeof dp);
    dp[0][0] = 0;
    int p = 50000;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= p; j++) {
            int& t = dp[i & 1][j];
            t = dp[(i - 1) & 1][j];
            if (j + a[i] <= p)
                t = max(t, dp[(i - 1) & 1][j + a[i]] + a[i]);
            t = max(t, dp[(i - 1) & 1][abs(j - a[i])] + a[i]);
        }
    }
    cout << dp[n & 1][0] << endl;
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i], m += a[i];

    solve1();
    solve2();
    solve3();
    solve4();

    return 0;
}

贪心

P1090 [NOIP2004 提高组] 合并果子

\(n\) 堆果子经过 \(n-1\) 次合并后, 剩下一堆,把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。求最小消耗体力。
输入格式:第一行是一个整数 n ,表示果子的种类数。
第二行包含 n个整数,第 i 个整数 \(a_i\) 是第 i 种果子的数目。
输出格式:最小的体力耗费值。输入数据保证这个值小于 \(2^{31}\)

说明/提示:\(1 ≤ n ≤ 10000, 1 ≤ a_i ≤ 20000\)

【分析】贪心策略:每次选择最小的两个元素进行合并。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;

int main() {
    priority_queue<int, vector<int>, greater<int>> q;
    int n, x, ans = 0; cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> x, q.push(x);
    while (q.size() > 1) {
        auto a = q.top(); q.pop();
        auto b = q.top(); q.pop();
        q.push(a + b), ans += a + b;
    }
    cout << ans;
    return 0;
}

P1803 凌乱的yyy / 线段覆盖

\(n\) 个比赛,每个比赛的开始、结束的时间点是知道的,最多能参加几个比赛。
如果要参加一个比赛必须善始善终,而且不能同时参加 \(2\) 个及以上的比赛。

输入格式:第一行是一个整数 \(n\),接下来 \(n\) 行每行是 \(2\) 个整数 \(a_{i},b_{i}\ (a_{i}<b_{i})\),表示比赛开始、结束的时间。
输出格式:一个整数最多参加的比赛数目。

数据范围:对于 \(100\%\) 的数据,\(1\le n \le 10^{6}\)\(0 \le a_{i} < b_{i} \le 10^6\)

【分析】贪心策略:先结束的优先选择。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
int n;
struct T {
    int l, r;
    bool operator<(const T& t) const { return r < t.r; }
} a[N];

int main() {
    cin >> n;
    for (int i = 1, l, r; i <= n; i++)
        cin >> l >> r, a[i] = {l, r};
    sort(a + 1, a + 1 + n);
    int ed = 0, ans = 0;
    for (int i = 1; i <= n; i++)
        if (a[i].l >= ed) ans++, ed = a[i].r;
    cout << ans << endl;
    return 0;
}

P1190 [NOIP2010 普及组] 接水问题

\(m\) 个龙头,\(n\) 名同学准备接水,他们的初始接水顺序已经确定。将这些同学按接水顺序从 \(1\)\(n\) 编号,\(i\) 号同学的接水量为 \(w_i\)。接水开始时,\(1\)\(m\) 号同学各占一个水龙头,并同时打开水龙头接水。当其中某名同学 \(j\) 完成其接水量要求 \(w_j\) 后,下一名排队等候接水的同学 \(k\) 马上接替 \(j\) 同学的位置开始接水。这个换人的过程是瞬间完成的,且没有任何水的浪费。即 \(j\) 同学第 \(x\) 秒结束时完成接水,则 \(k\) 同学第 \(x+1\) 秒立刻开始接水。若当前接水人数 \(n'\) 不足 \(m\),则只有 \(n'\) 个龙头供水,其它 \(m - n'\) 个龙头关闭。

现在给出 \(n\) 名同学的接水量,按照上述接水规则,问所有同学都接完水需要多少秒。

输入格式:
第一行两个整数 \(n\)\(m\),用一个空格隔开,分别表示接水人数和龙头个数。
第二行 \(n\) 个整数 \(w_1,w_2,\ldots,w_n\),每两个整数之间用一个空格隔开,\(w_i\) 表示 \(i\) 号同学的接水量。
输出格式:一个整数,表示接水所需的总时间。

【数据范围】\(1 \le n \le {10}^4\)\(1 \le m \le 100\)\(m \le n\), \(1 \le w_i \le 100\)

【分析】贪心策略:每次选择当前最先完成的水龙头打水。
使用 w[i] 记录当前水龙头的用时,每次用最小的打水,最后用时最大的就是答案

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
int n, m, w[N], a[N];

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> w[i];
    for (int i = m + 1; i <= n; i++) {
        sort(w + 1, w + 1 + m);
        w[1] = w[1] + w[i];
    }
    sort(w + 1, w + 1 + m);
    cout << w[m] << endl;
    return 0;
}

P1106 删数问题

键盘输入一个高精度的正整数 \(n\)(不超过 \(250\) 位),去掉其中任意 \(k\) 个数字后剩下的数字按原左右次序将组成一个新的非负整数。编程对给定的 \(n\)\(k\),寻找一种方案使得剩下的数字组成的新数最小。

输入格式:输入两行正整数。
第一行输入一个高精度的正整数 \(n\)
第二行输入一个正整数 \(k\),表示需要删除的数字个数。
输出格式:输出一个整数,最后剩下的最小数。

数据范围:用 \(\operatorname{len}(n)\) 表示 \(n\)位数,保证 \(1 \leq k < \operatorname{len}(n) \leq 250\)

【分析】怎么才能维护最小的数,考虑一个单调递增的数列即可

方法1:维护一个单调递增栈,如果元素递增时删除的数量不足 k,那么删除栈顶元素即可;最后去除前导0.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
char st[N];  // 递增的元素
int hh, k;
string s;

int main() {
    cin >> s >> k;
    // 1. 构造升序序列 st  --- 单调递增栈
    for (auto u : s) {
        while (k && hh && st[hh] > u)
            hh--, k--;
        st[++hh] = u;
    }
    // 2. 倒着删除 k 个字符
    while (k)
        hh--, k--;

    // 3. 字符串结尾 '\0'
    st[hh + 1] = '\0';

    // 4. 去除前导 0
    int x = 1;
    while (x < hh && st[x] == '0')
        x++;
    // 5. 答案 st[x, hh]
    cout << st + x;
    return 0;
}

方法2:字符串模拟

#include <iostream>
#include <string>
using namespace std;
const int N = 1e5 + 5;
typedef long long ll;

int main() {
    string s; int k;
    while (cin >> s >> k) {
        while (k) {
            bool flag = 1;
            for (int i = 0; i < s.size(); i++)
                if (s[i] > s[i + 1]) {
                    s.erase(i, 1), k--, flag = 0;
                    break;
                }
            // 前导 0
            while (s.find('0') == 0 && s.size() > 1) s.erase(0, 1);
            if (flag) break;
        }
        for (int i = s.size(); k && i > 1; i--)
            s.erase(i, 1), k--;
        cout << s << endl;
    }
    return 0;
}

P2878 [USACO07JAN] Protecting the Flowers S

\(n\) 头奶牛跑到 FJ 的花园里去吃花儿了,它们分别在距离牛圈 \(T_i\)(这里指 FJ 到那里需要 \(T_i\) 分钟) 处吃花,每分钟会吃掉 \(D_i\) 朵花,FJ 现在要将它们给弄回牛圈,但是他每次只能弄一头回去,来回用时总共为 \(2 \times T_i\) 分钟,在这段时间内,其它的奶牛会继续吃 FJ 的花,速度保持不变,当然正在被赶回牛圈的奶牛不能继续吃了。现在求在最好的方案下奶牛吃掉花的最小朵数。

【分析】贪心策略:优先选择赶回去代价小的。

假设对于牛 a,b 而言,如果
先赶牛 a,需要代价 \(2×a.t×b.d\)
先赶牛 b,需要代价 \(2×b.t×a.d\)

选择先赶代价小的,所以\(2×a.t×b.d < 2×b.t×a.d\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
typedef long long ll;

struct T {
    int t, d;
    bool operator<(const T& temp) const {
        return 2 * t * temp.d < 2 * temp.t * d;
    }
} t[N];
ll n, sum = 0, ans = 0;

int main() {
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> t[i].t >> t[i].d;
    for (int i = 0; i < n; i++)
        sum += t[i].d;
    sort(t, t + n);
    for (int i = 0; i < n; i++) {
        sum -= t[i].d;
        ans += sum * 2 * t[i].t;
    }
    cout << ans;
    return 0;
}

P4712 「生物」能量流动

生物课上,小 F 学习到了食物链、食物网的相关内容。
他学到,能量是逐级递减的。比如在食物链上,两个链接起来的生物 \(A \rightarrow B\)(意思是 \(B\)\(A\))之间的能量传递效率最多只有 \(\frac 1 5\);而研究种间关系,能够使能量流向对人类最有益的部分。
现在,小 F 想研究一下能量流动的关系,于是他在脑海里创造了一个生态的系统。

在这个生态的系统里,有 \(n+2\) 种生物,其中 \(0\) 是生产者,整个生态系统所流经的能量由它固定;你是贪婪的顶级掠食者 \(n + 1\),可以捕食所有生物;其他的掠食者 \([1, n]\) 各有各的喜好,只会捕食编号在 \([0, r_i]\) 的生物。由于天然形成的强弱顺序,上述关系满足 \(r_i \leq r_{i + 1}(1 \leq i < n),\) \(r_i < i(1 \leq i \leq n)\)

每种动物需要摄取至少 \(a_i\) 单位能量才能存活;一个生物摄取到的能量可以传递给捕食者,但传递效率都不超过 \(\frac 1 5\)。也就是说,假设该动物捕获了 \(b_i\) 单位的能量,所有捕食它的掠食者得到的能量总和 \(c_i\),那么有:

  • \(b_i \geq a_i\)
  • \(c_i \leq \frac 1 5 b_i\)

你希望知道,在所有生物都存活的情况下,在最优情况下你能获取到的最大能量是多少。

输入格式:
输入第一行两个整数 \(n, a_0(1 \leq n \leq 10 ^ 5, 1 \leq a_0 \leq 10 ^ 9)\)\(a_0\) 是生产者固定的能量值。
接下来 \(n\) 行,每行 \(2\) 个正整数,表示 \(a_i, r_i(1 \leq a_i \leq 10 ^ 9)\)
数据保证,\(0\leq r_i < i, r_i \leq r_{i + 1}\)

输出格式:
输出一行一个浮点数,表示你——顶级掠食者——能得到的最大能量。如果不能使得所有生物存活(包括你自己),请输出 \(-1\)
你的答案与参考答案的相对误差或者绝对误差不超过 \(10 ^ {-8}\) 即被认为是正确的。如果你的程序是正确的,可以不用考虑精度问题。

子任务 \(1(21 \mathrm{pts}) : n \leq 100\)
子任务 \(2(89 \mathrm{pts}) : n \leq 10 ^ 5\)

【分析】考虑都能活+顶级掠食者能得到最大的能量,那么前面的能活即可。
为避免浮点数误差,可以求最小转换前的能量 \(5×a_i\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
int n, a[N], r[N];

int main() {
    cin >> n >> a[0];
    for (int i = 1; i <= n; i++)
        cin >> a[i] >> r[i];
    int now = 0, sum = a[0];
    for (int i = 1; i <= n; i++) {
        while (now < r[i]) sum += a[++now];
        if (sum < 5 * a[i]) {
            puts("-1"); return 0;
        }
        sum -= a[i] * 5;
    }
    while (now < n) sum += a[++now];
    printf("%.9lf", sum * 0.2);
    return 0;
}
posted @ 2024-08-12 08:34  HelloHeBin  阅读(68)  评论(0编辑  收藏  举报