标签: 贪心

[CF3B]Lorry 题解

[CF3B]Lorry 题解

题目地址:Codeforces:Problem – 3B – Codeforces、洛谷:CF3B Lorry – 洛谷 | 计算机科学教育新生态

题目描述

A group of tourists is going to kayak and catamaran tour. A rented lorry has arrived to the boat depot to take kayaks and catamarans to the point of departure. It’s known that all kayaks are of the same size (and each of them occupies the space of 1 cubic metre), and all catamarans are of the same size, but two times bigger than kayaks (and occupy the space of 2 cubic metres).

Each waterborne vehicle has a particular carrying capacity, and it should be noted that waterborne vehicles that look the same can have different carrying capacities. Knowing the truck body volume and the list of waterborne vehicles in the boat depot (for each one its type and carrying capacity are known), find out such set of vehicles that can be taken in the lorry, and that has the maximum total carrying capacity. The truck body volume of the lorry can be used effectively, that is to say you can always put into the lorry a waterborne vehicle that occupies the space not exceeding the free space left in the truck body.

题意简述

有一辆能够装载容量为$v$的货车,共有$n$个货物待装。货物分为两种类型,类型1所占容量为1,类型2所占容量为2,同种类型的货物的价值可能不同。求货车最多可以装载货物的最大价值之和。

输入输出格式

输入格式:
The first line contains a pair of integer numbers n and v (1 ≤ n ≤ 10^5; 1 ≤ v ≤ 10^9), where n is the number of waterborne vehicles in the boat depot, and v is the truck body volume of the lorry in cubic metres. The following n lines contain the information about the waterborne vehicles, that is a pair of numbers ti, pi (1 ≤ ti ≤ 2; 1 ≤ pi ≤ 10^4), where ti is the vehicle type (1 – a kayak, 2 – a catamaran), and pi is its carrying capacity. The waterborne vehicles are enumerated in order of their appearance in the input file.

输出格式:
In the first line print the maximum possible carrying capacity of the set. In the second line print a string consisting of the numbers of the vehicles that make the optimal set. If the answer is not unique, print any of them.

输入输出样例

输入样例#1:

3 2
1 2
2 7
1 3

输出样例#1:

7
2

题解

首先看到这个题就有一种强烈的直觉,即按单位容量的价值(即所谓的“性价比”)排序,先从大到小贪心地装,此时会遇到三种情况:货物的总占用容量小于货车容量,或者货车容量刚好用完,或者还剩1没有用。
对于第一种情况,直接全装进去就完事了。
对于第二种情况,直接按贪心的方案输出即可,因为根据贪心的思路,用单位价值较低的货物替换只会让方案变得更劣。
对于第三种情况,有两种选择:用未装进去的占容量为1的货物填这个空,或者把前面的某个占容量为1的货物替换为一个占容量为2的货物。第一个选择中,填空的货物肯定是未装之中价值最大的那个,而第二个选择中,肯定是用价值最大的2货物替换价值最小的1货物,这些都可以贪心地做。判断一下哪种选择更优即可。
思路比较好想,不过细节比较多,代码稍微有点难度。

代码

// Code by KSkun, 2019/6
#include <cstdio>
#include <cctype>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    LL res = 0, neg = 1; char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = res * 10 + c - '0';
    return res * neg;
}

inline char readsingle() {
    char c;
    while(!isgraph(c = fgc())) {}
    return c;
}

const int MAXN = 100005;

int n, v;

struct Node {
    int no, t, p;
    bool operator>(const Node &rhs) const {
        return double(p) / t > double(rhs.p) / rhs.t;
    }
} a[MAXN];

int main() {
    n = readint(); v = readint();
    for(int i = 1; i <= n; i++) {
        a[i].t = readint();
        a[i].p = readint();
        a[i].no = i;
    }
    std::sort(a + 1, a + n + 1, std::greater<Node>());
    int mn1 = 1e9, mn1n = 0, sum = 0, i;
    for(i = 1; i <= n; i++) {
        if(v < a[i].t) break;
        if(a[i].t == 1 && a[i].p < mn1) {
            mn1 = a[i].p; mn1n = i;
        }
        v -= a[i].t;
        sum += a[i].p;
    }
    int mx1 = 0, mx1n = 0, mx2 = 0, mx2n = 0;
    for(int j = i; j <= n; j++) {
        if(a[j].t == 1 && a[j].p > mx1) {
            mx1 = a[j].p; mx1n = j;
        }
        if(a[j].t == 2 && a[j].p > mx2) {
            mx2 = a[j].p; mx2n = j;
        }
    }
    if(v == 0 || (i == n && v >= a[n].t)) {
        printf("%d\n", sum);
        for(int j = 1; j < i; j++) printf("%d ", a[j].no);
    } else {
        int ans1 = 0, ans2 = 0;
        if(mx1 != 0) ans1 = sum + mx1;
        if(mx2 != 0) ans2 = sum - mn1 + mx2;
        int ans = std::max(ans1, ans2);
        ans = std::max(ans, sum);
        printf("%d\n", ans);
        for(int j = 1; j < i; j++) {
            if(ans != sum && ans != ans1 && ans == ans2 && j == mn1n) continue;
            printf("%d ", a[j].no);
        }
        if(ans != sum && ans == ans1) printf("%d ", a[mx1n].no);
        if(ans != sum && ans != ans1 && ans == ans2) printf("%d ", a[mx2n].no);
    }
    return 0;
}
Codeforces Round #495 (Div. 2) 赛后总结

Codeforces Round #495 (Div. 2) 赛后总结

比赛地址:Dashboard – Codeforces Round #495 (Div. 2) – Codeforces
官方题解:Codeforces Round #495 (Div. 2) — Editorial – Codeforces

1004A Sonya and Hotels

题意简述

一维空间坐标轴上有$n$个整点位置修建了酒店,现在想在一个未建酒店的整点位置修一个新酒店,要求新酒店离最近的酒店的距离等于$d$,求新酒店可以选择的坐标数量。
数据范围:$1 \leq 100, 1 \leq d \leq 10^9$

思路

枚举每两个酒店之间的距离,$距离=2d$的时候对答案有1的贡献,$>2d$的时候有2的贡献,首尾各有1的贡献,统计一下就好。
复杂度$O(n)$。

代码

// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 105;

int n, d, x[MAXN];

int main() {
    n = readint(); d = readint();
    for(int i = 1; i <= n; i++) {
        x[i] = readint();
    }
    int ans = 2;
    for(int i = 2; i <= n; i++) {
        if(x[i] - x[i - 1] == 2 * d) ans++;
        if(x[i] - x[i - 1] > 2 * d) ans += 2;
    }
    printf("%d", ans);
    return 0;
}

1004B Sonya and Exhibition

题意简述

要放$n$盆花成一排,每个位置有两种选择,有$m$个人参观这些花,他们会参观一个连续区间的花,参观的美丽度定义为区间两种花的个数的乘积,求安排方案使得美丽度之和最大。
数据范围:$1 \leq n, m \leq 10^3$

思路

$$ n^2 > (n+1)(n-1) > (n+2)(n-2) > \cdots $$
因此我们可以直接输出一个类似0101010101010101这样的序列就好了。
复杂度$O(n)$。

代码

// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 1005;

int n, m, l[MAXN], r[MAXN];

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= m; i++) {
        l[i] = readint(); r[i] = readint();
    }
    LL sum1 = 0, sum2 = 0;
    for(int i = 1; i <= m; i++) {
        int cnt1[2] = {0, 0}, cnt2[2] = {0, 0};
        for(int j = l[i]; j <= r[i]; j++) {
            cnt1[j & 1]++;
            cnt2[(j & 1) ^ 1]++;
        }
        sum1 += 1ll * cnt1[0] * cnt1[1];
        sum2 += 1ll * cnt2[0] * cnt2[1];
    }
    if(sum1 > sum2) {
        fprintf(stderr, "%lld\n", sum1);
        for(int i = 1; i <= n; i++) {
            putchar('0' + (i & 1));
        }
    } else {
        fprintf(stderr, "%lld\n", sum2);
        for(int i = 1; i <= n; i++) {
            putchar('0' + ((i & 1) ^ 1));
        }
    }
    return 0;
}

1004C Sonya and Robots

题意简述

有一个长为$n$的序列,两个机器人从序列的两段开始相向而行,遇到第一个与机器人中存储的数字相同的数字时就在该位置停下,你可以给机器人写入不同的数字,求让它们不相遇的数字对数。
数据范围:$1 \leq n \leq 10^5$

思路

我们从左向右扫这个序列,对于一个之前没有出现过的数字,显然它会与后面的不重复数字产生贡献,那么答案就是这些贡献的和。我们只需要知道一个位置的后面是否还有与这个位置相同的数字,动态地维护后面的不重复数字种数以及前面出现过哪些数字就可以了。
复杂度$O(n)$。

代码

// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 100005;

int n, a[MAXN], nxt[MAXN], head[MAXN];
bool vis[MAXN];

int main() {
    n = readint();
    int cnt = 0;
    LL ans = 0;
    for(int i = 1; i <= n; i++) {
        a[i] = readint();
        if(!vis[a[i]]) {
            vis[a[i]] = true;
            head[a[i]] = i;
            cnt++;
        } else {
            nxt[head[a[i]]] = i;
            head[a[i]] = i;
        }
    }
    memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= n; i++) {
        if(!nxt[i]) {
            cnt--;
        }
        if(!vis[a[i]]) ans += cnt;
        vis[a[i]] = true;
    }
    printf("%lld", ans);
    return 0;
}

1004D Sonya and Matrix

题意简述

有一个矩阵,矩阵中有1个权值为0的格子,而其他格子的权值为到0权格子的曼哈顿距离。现在给你一个长为$t$的序列,是一个包含某一个矩阵里面的所有权值的乱序可重排列。现在你要求出原来矩阵的大小$n \times m$以及0权的位置$(x, y)$。
数据范围:$1 \leq t \leq 10^6$

思路

首先,我们可以用序列统计每个数字出现的次数。在完整的图形中,我们会发现每个数字形成了菱形或者说正方形的形状,如下所示。

          5
        5 4 5
      5 4 3 4 5
    5 4 3 2 3 4 5
  5 4 3 2 1 2 3 4 5
5 4 3 2 1 0 1 2 3 4 5
  5 4 3 2 1 2 3 4 5
    5 4 3 2 3 4 5
      5 4 3 4 5
        5 4 5
          5

我们可以求出序列中的最大数字以及最大的在矩阵中完整地出现了它的菱形的数字,显然,最大数字应该在角上,由于图形的对称性,四个角是等价的,我们暂且让它在左上角$(1, 1)$。
当我们发现矩形的大小并无法让0与最大数共存的时候,显然情况不合法,这种情况的判断,我们可以使用对角线曼哈顿距离(即图形中的最长曼哈顿距离)来判断,即$n+m-2 < mx$时不合法。
另外的不合法情况就是当确定了$n, m, x, y$以后,我们可以计算出最大的整个菱形都包含进来的数字,这个数字是$\min \{ x-1, y-1, n-x, m-y \}$,再把不合法的情况扔掉就好。
剩下的合法情况已经很少了,我们直接暴力$O(t)$地验证就好。

代码

// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cmath>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 1000005;

int t, mx, cnt[MAXN], cnt2[MAXN];

inline int border(int n, int m, int x, int y) {
    return std::min(std::min(x - 1, y - 1), std::min(n - x, m - y));
}

inline bool check(int n, int m, int x, int y) {
    memset(cnt2, 0, sizeof(cnt2));
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            cnt2[abs(x - i) + abs(y - j)]++;
        }
    }
    for(int i = 1; i <= t; i++) {
        if(cnt2[i] != cnt[i]) return false;
    }
    return true;
}

int main() {
    t = readint();
    for(int i = 1; i <= t; i++) {
        int a = readint();
        cnt[a]++;
        mx = std::max(mx, a);
    }
    int lim = 0;
    for(int i = 1; i <= t; i++) {
        if(cnt[i] != i * 4) {
            lim = i - 1; break;
        }
    }
    for(int n = 1; n * n <= t; n++) {
        if(t % n) continue;
        int m = t / n;
        if(n + m - 2 < mx) continue;
        for(int j = 1; j <= n; j++) {
            int k = mx - j + 2;
            if(k > m || k < 1) continue;
            if(border(n, m, j, k) != lim) continue;
            if(check(n, m, j, k)) {
                printf("%d %d\n%d %d", n, m, j, k);
                return 0;
            }
        }
    }
    puts("-1");
    return 0;
}

1004E Sonya and Ice Cream

题意简述

给你一棵$n$个点的树,要求从中选出一条不超过$k$个点的路径,使得树的其他点到这条路径的最短距离最大值最小。
数据范围:$1 \leq k \leq n \leq 10^5$

思路

首先,我们可以证明,这条路径在包含树的中心点的时候比较优,中心点定义为到这个点最远的点距离最小的点,显然,这个点应该在直径的中心位置,可以$O(n)$求出来。
答案具有二分性质,所以我们二分答案,接着考虑如何验证。我们可以从中心点开始DFS,每次扩展出去一个点,如果发现一个点的子树中最远的点到它的距离大于答案,则这个点一定要被加入路径。如果一个点有多个儿子一定要被加入路径(中心点特殊考虑,它允许有2个儿子必须被加入路径)则不合法。如果必须被加入路径的点数多于$k$则不合法,判断一下就好。
复杂度$O(n \log n)$。

代码

// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>

#include <algorithm>
#include <vector>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 100005;

struct Edge {
    int to, w;
};
std::vector<Edge> gra[MAXN];

int n, k, du, dv, ct, fa[MAXN], dis[MAXN];

inline void dfs_dis(int u) {
    for(auto e : gra[u]) {
        if(e.to == fa[u]) continue;
        dis[e.to] = dis[u] + e.w;
        fa[e.to] = u;
        dfs_dis(e.to);
    }
}

inline void diameter() {
    dfs_dis(1);
    for(int i = 1; i <= n; i++) {
        if(dis[i] > dis[du]) du = i;
    }
    dis[du] = 0;
    memset(fa, 0, sizeof(fa));
    dfs_dis(du);
    for(int i = 1; i <= n; i++) {
        if(dis[i] > dis[dv]) dv = i;
    }
}

inline void center() {
    for(int i = dv; i; i = fa[i]) {
        if(std::max(dis[ct], dis[dv] - dis[ct]) > std::max(dis[i], dis[dv] - dis[i])) {
            ct = i;
        }
    }
}

int cnt;
bool success;

inline int dfs_check(int u, int fa, int lim) {
    int res = 0, big = 0;
    for(auto e : gra[u]) {
        if(e.to == fa) continue;
        int dis = dfs_check(e.to, u, lim);
        if(dis != -1 && dis + e.w > lim) {
            cnt++; big++;
        }
        if(dis == -1) big++;
        else res = std::max(res, dis + e.w);
    }
    if((u == ct && big > 2) || (u != ct && big > 1)) {
        success = false;
    }
    if(big || u == ct) {
        cnt++; return -1;
    }
    return res;
}

inline bool check(int mid) {
    cnt = 0; success = true;
    dfs_check(ct, 0, mid);
    return success && cnt <= k;
}

int main() {
    n = readint(); k = readint();
    for(int i = 1, u, v, w; i < n; i++) {
        u = readint(); v = readint(); w = readint();
        gra[u].push_back(Edge {v, w});
        gra[v].push_back(Edge {u, w});
    }
    diameter(); center();
    int l = -1, r = 1e9, mid;
    while(r - l > 1) {
        mid = (l + r) >> 1;
        if(check(mid)) r = mid; else l = mid;
    }
    printf("%d", r);
    return 0;
}

1004F Sonya and Bitwise OR

题意简述

维护一个长为$n$的序列,有$m$次操作,支持两种操作

  1. 单点修改
  2. 区间询问有多少连续子序列的异或和不小于$x$(对于所有的询问$x$都相同)

数据范围:$1 \leq n, m \leq 10^5$

思路

咕咕咕。

代码

咕咕咕。

[POI2003]Chocolate 题解

[POI2003]Chocolate 题解

题目地址:BZOJ:Problem 2430. — [Poi2003]Chocolate

题目描述

有一块n*m的矩形巧克力,准备将它切成n*m块。巧克力上共有n-1条横线和m-1条竖线,你每次可以沿着其中的一条横线或竖线将巧克力切开,无论切割的长短,沿着每条横线切一次的代价依次为y1,y2,…,yn-1,而沿竖线切割的代价依次为x1,x2,…,xm-1。例如,对于下图6*4的巧克力,
11(2) - [POI2003]Chocolate 题解
我们先沿着三条横线切割,需要3刀,得到4条巧克力,然后再将这4条巧克力沿竖线切割,每条都需要5刀,则最终所花费的代价为y1+y2+y3+4*(x1+x2+x3+x4+x5)。
当然,上述简单切法不见得是最优切法,那么怎样切割该块巧克力,花费的代价最少呢?

题意简述

要把一个大矩形横着切n-1刀竖着切m-1刀分成n*m个小矩形,从左到右从上到下每个位置切一刀都有一个代价,每一次对一整块(不论大小,无法一刀切断多块)切一次都会产生一次代价,求切割的最小代价。

输入输出格式

输入格式:
第一行为两个整数n和m。
接下来n-1行,每行一个整数,分别代表x1,x2,…,xn-1。
接下来m-1行,每行一个整数,分别代表y1,y2,…,ym-1。

输出格式:
输出一整数,为切割巧克力的最小代价。

输入输出样例

输入样例#1:

6 4
2
1
3
1
4
4
1
2

输出样例#1:

42

说明

30%的数据,n<=100,m<=100
100%的数据,n<=10000,m<=10000

题解

贪心。
一定是先切掉代价较大的位置。考虑证明它,对于平行的刀切位置,在一个序列中先切代价大的,由于每一刀要乘上与位置垂直的位置已经切了几刀这个数字,因此越靠前乘的数字越小,总和越小。对于垂直的两个刀切位置,先切代价较大的那个,若先切较小的,较大的乘的数字会+1,比先切较大的更劣。
由于需要排序,复杂度O(n \log n)

代码

// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>

#include <algorithm>
#include <vector>
#include <functional>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 20005;

struct Node {
    int w;
    bool type;
    inline bool operator>(const Node &rhs) const {
        return w > rhs.w;
    }
};

int n, m;
std::vector<Node> vec;

int main() {
    n = readint(); m = readint();
    for(int i = 1, t; i < n; i++) {
        t = readint(); vec.push_back(Node {t, 0});
    }
    for(int i = 1, t; i < m; i++) {
        t = readint(); vec.push_back(Node {t, 1});
    }
    std::sort(vec.begin(), vec.end(), std::greater<Node>());
    int ch = 1, cv = 1, ans = 0;
    for(int i = 0; i < vec.size(); i++) {
        ans += vec[i].w * (vec[i].type ? ch : cv);
        if(vec[i].type) cv++; else ch++;
    }
    printf("%d", ans);
    return 0;
}