标签 贪心 下的文章

#include <iostream>
#include <set>
using namespace std;
int main()
{
    int n;
    cin >> n;
    set<int> s;
    s.insert(0);
    for (int i = 0; i < n; i ++)
    {
        int a;
        cin >> a;
        if (a < (*s.rbegin()))
        {
            s.erase(s.upper_bound(a));
        }
        s.insert(a);
    }
    cout << s.size() - 1 << endl;
    return 0;
}

启示:

用到了set的特性。除了去重之外,还有一个自动按照从小到大排序,和map一样,因为底层是红黑树,一种平衡二叉搜索树。所以可以有rbegin()取最后一个元素,就是最大元素。这里的逻辑就是让每一个轨道都保持一个单调上升的排序,这样再出去的时候就可以按照从大到小排序的出去(从右往左看)。所以对于每一个数来说如果比最大的还要大就单独开一个序列,如果小于最大的,说明可以找到一个序列使得放进去之后组成的排列时单调上升的,为了可以用最少的轨道,这个放进去的序列最好是最后一个数刚好大于要放进去的数,这里使用到了贪心的思想。为了维护每一个单调上升的序列,只要维护一个序列的最后一个值就好,因为前面的值一定比他大,而且不会拿来做比较,所以可以不看,因此set中实际上是维护了一个所有存在的轨道的序列中的最小值的一个不重复集合。而对于要放入的数来说,可以用upper_bound来找到第一个大于他的数,这个数就是要放入的轨道的最后一个值,将当前数放进去,本质上就是在set中将这个最后的值替换。而因为set中没有索引,但是可以自动排序,根据这一特性,为了完成替换这个操作,我们可以将这个最后一个值在set中删去,最后再将a放去set即可,这就在形式上和另外一种情况保持了统一。最后为了保证形式上的统一,不对第一个数特判,即set为空的情况,可以单独放入一个元素0,因为0一定比所有1到n的数小,所以不会被替换,就不会影响答案。最后的答案要求输出轨道的数量,其实就是set中元素的数量,因为有0的存在,所以实际上的答案是s.size() - 1.

题目:

火车站的列车调度铁轨的结构如下图所示。

188

两端分别是一条入口(Entrance)轨道和一条出口(Exit)轨道,它们之间有N条平行的轨道。每趟列车从入口可以选择任意一条轨道进入,最后从出口离开。在图中有9趟列车,在入口处按照{8,4,2,5,3,9,1,6,7}的顺序排队等待进入。如果要求它们必须按序号递减的顺序从出口离开,则至少需要多少条平行铁轨用于调度?

输入格式:

输入第一行给出一个整数N (2 ≤ N ≤105),下一行给出从1到N的整数序号的一个重排列。数字间以空格分隔。

输出格式:

在一行中输出可以将输入的列车按序号递减的顺序调离所需要的最少的铁轨条数。

输入样例:

9
8 4 2 5 3 9 1 6 7

输出样例:

4

url: https://www.luogu.com.cn/problem/P1284

tag:
动态规划,贪心,背包DP

思路:
f[k][i][j] 来表示前k块木板能否组成三角形。所以状态转移方程为:
f[k][i][j] = f[k - 1][i - a[k]][j] || f[k - 1][i][j - a[k]] || f[k][i][j]
因为转移时只跟 f[k - 1][][] 层的数据有关,所以可以用类似01背包问题的方式来简化掉这一维。初始化 f[0][0] = 1 之后对于每一个可能的结果判断是否为合法三角形,然后计算面积更新最大值即可。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 50;
int n, sum;
int a[N];
bool f[N * N][N * N];
long long ans;
bool check(int a, int b, int c)
{
    return a + b > c && a + c > b && b + c > a;
}
double resolue(int a, int b, int c)
{
    double p = (a * 1.0 + b + c) / 2;
    return sqrt(p * (p - a) * (p - b) * (p - c));
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        sum += a[i];
    }
    f[0][0] = 1;
    for (int k = 1; k <= n; k ++)
        for (int i = sum / 2; i >= 0; i --)
            for (int j = sum / 2; j >= 0; j --)
            {
                if (i - a[k] >= 0 && f[i - a[k]][j]) f[i][j] = 1;
                if (j - a[k] >= 0 && f[i][j - a[k]]) f[i][j] = 1;
            }
    double res = -1;
    for (int i = sum / 2; i > 0; i --)
        for (int j = sum / 2; j > 0; j --)
        {
            if (!f[i][j]) continue;
            if (!check(i, j, sum - i - j)) continue;
            res = max(res, resolue(i, j, sum - i - j));
        }
    if (res == -1) cout << -1 << endl;
    else cout << (long long)(res * 100) << endl;
    return 0;
}

url: https://www.luogu.com.cn/problem/P2672

tag:
NOIP2015 普及组,贪心,线段树,树状数组,前缀和,动态规划。

思路:
思路都是用贪心的策略,有两种情况,一种是取前x个a最大的客户,第二种是取前x - 1个a最大的客户,然后再从第i到n个中选择距离最远的一个去替换第x个a最大的客户,两种情况取最大值输出即可。如何求呢?这里用动态规划和前缀和来求,具体可以看代码。看其他题解有用线段树来做,但是比较麻烦,就上动规和前缀和了。

代码:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
long long sum[N], c[N], h[N];
struct client {
    int s, a;
}Client[N];
bool cmp(client &a, client &b)
{
    return a.a > b.a;
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++) cin >> Client[i].s;
    for (int i = 1; i <= n; i ++) cin >> Client[i].a;
    sort(Client + 1, Client + 1 + n, cmp);
    for (int i = 1; i <= n; i ++)
    {
        sum[i] = sum[i - 1] + Client[i].a;
        h[i] = max(h[i - 1], (long long)Client[i].s * 2);
    }
    for (int i = n; i >= 1; i --)
    {
        c[i] = max(c[i + 1], 2LL * Client[i].s + Client[i].a);
    }
    for (int i = 1; i <= n; i ++)
    {
        cout << max(sum[i] + h[i], sum[i - 1] + c[i]) << '\n';
    }
    return 0;
}

url: https://www.luogu.com.cn/problem/P1080

tag:
NOIP2012 提高组,贪心,高精度

思路:
用贪心的策略,将大臣按照左右手金币数量的乘积从小到大排序,贪心证明: https://www.luogu.com.cn/article/2xwnkq6p . 然后因为数据范围比较大又用到了乘除所以需要使用高精度,同时配合写一个比较函数。

代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1e4 + 10;
int n;
typedef pair<int, int> PII;
vector<PII> d;
bool cmp(PII &a, PII &b)
{
    return a.first * a.second < b.first * b.second;
}
vector<int> mul(vector<int> &A, int b)
{
    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size(); i ++)
    {
        t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }
    while (t)
    {
        C.push_back(t % 10);
        t /= 10;
    }
    while (!C.back() && C.size() != 1) C.pop_back();
    return C;
}
vector<int> div(vector<int> &A, int b)
{
    vector<int> C;
    reverse(A.begin(), A.end());
    int t = 0;
    for (int i = 0; i < A.size(); i ++)
    {
        t = t * 10 + A[i];
        C.push_back(t / b);
        t %= b;
    }
    reverse(C.begin(), C.end());
    while (!C.back() && C.size() > 1) C.pop_back();
    reverse(A.begin(), A.end());
    return C;
}
bool rescmp(vector<int> &A, vector<int> &B)
{
    if (A.size() != B.size()) return A.size() > B.size();
    else
    {
        for (int i = A.size() - 1; i >= 0; i ++)
        {
            if (A[i] == B[i]) continue;
            else return A[i] > B[i];
        }
    }
}
int main()
{
    cin >> n;
    PII king;
    cin >> king.first >> king.second;
    for (int i = 0; i < n; i ++)
    {
        int a, b;
        cin >> a >> b;
        d.push_back({a, b});
    }
    sort(d.begin(), d.end(),cmp);
    d.insert(d.begin(),king);
    vector<int> res(1, 0);
    vector<int> tmp(1, 1);
    for (int i = 1; i < d.size(); i ++)
    {
        tmp = mul(tmp,d[i - 1].first);
        auto tmmp = div(tmp, d[i].second);
        if (rescmp(tmmp, res)) res = tmmp;
    }
    for (int i = res.size() - 1; i >= 0; i --) cout << res[i];
    cout << endl;
    return 0;
}

url: https://www.luogu.com.cn/problem/P1281

tag:
二分,贪心

思路:
求可以满足条件的最小值,所以可以想到用二分来做。二分的是复制时间,所以范围是在0到全部的页数。二分的思路是利用这个复制时间来求出一个需要的人数,将这个人数和题目所给的认识做一个比较,因为要满足条件,所以人数需要小于等于题目给的人数。可以知道如果复制时间越大,表示一个人可以抄的书越多,需要的人数越少,所以人数和复制时间是单调递减的关系。利用这个单调递减的关系,可以二分出一个复制时间,使得这个复制时间是当所需人数小于等于所需人数时,复制时间最小。然后就可以通过这个复制时间来求出每一个人所抄的书的起始范围。因为要求是让前面的人尽可能的少抄,所以就需要让后面的人多抄,因此再输出答案的时候用到了贪心的思想,从后往前遍历书,使得每次每个人都尽可能抄多一点的书,通过这种遍历方式实现题目要求的让前面的人尽可能少抄。在二分的时候也有用到这种遍历方式,但是后面写题解的时候觉得或许二分的时候不需要,因为只是求一个人数,没有前后之分。

代码:

#include <iostream>
using namespace std;
int m, k;
const int N = 550;
int a[N];
int x[N], y[N];
bool check(int x)
{
    int now = 1;
    int cnt = 0;
    for (int i = m - 1; i >= 0; i --)
    {
        if (cnt + a[i] > x)
        {
            cnt = 0;
            now ++;
        }
        cnt += a[i];
    }
    return now <= k;
}
int main()
{
    cin >> m >> k;
    int kk = k;
    int l = 0, r = 0;
    for (int i = 0; i < m; i ++)
    {
        cin >> a[i];
        r += a[i];
    }
    while (l < r)
    {
        int mid = (l + r) >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    int t = 0;
    y[k] = m; x[1] = 1;
    for (int i = m - 1; i >= 0; i --)
    {
        if (t + a[i] > l)
        {
            t = 0;
            x[k] = i + 2;
            y[--k] = i + 1;
        }
        t += a[i];
    }
    for (int i = 1; i <= kk; i ++)
    {
        cout << x[i] << ' ' << y[i] << '\n';
    }
    return 0;
}