标签: 贪心

[POI2005]SAM-Toy Cars 题解

[POI2005]SAM-Toy Cars 题解

题目地址:洛谷:【P3419】[POI2005]SAM-Toy Cars – 洛谷、BZOJ:Problem 1528. — [POI2005]sam-Toy Cars

题目描述

Jasio 是一个三岁的小男孩,他最喜欢玩玩具了,他有n 个不同的玩具,它们都被放在了很高的架子上所以Jasio 拿不到它们. 为了让他的房间有足够的空间,在任何时刻地板上都不会有超过k 个玩具. Jasio 在地板上玩玩具. Jasio’的妈妈则在房间里陪他的儿子. 当Jasio 想玩地板上的其他玩具时,他会自己去拿,如果他想玩的玩具在架子上,他的妈妈则会帮他去拿,当她拿玩具的时候,顺便也会将一个地板上的玩具放上架子使得地板上有足够的空间. 他的妈妈很清楚自己的孩子所以他能够预料到Jasio 想玩些什么玩具. 所以她想尽量的使自己去架子上拿玩具的次数尽量的少,应该怎么安排放玩具的顺序呢?

题意简述

Jasio有n个不同的玩具,有一段时间p内,每一单位时间他都想玩一个玩具,而地上只能放最多k个玩具。如果玩具不在地上,则他妈妈要帮他从架子上拿,如果此时地板上的玩具已经达到了k个,则还要拿一个玩具放回架子。求一种安排方式使得妈妈从架子上拿玩具的次数最少。

输入输出格式

输入格式:
第一行三个整数: n, k, p (1 <= k <= n <= 100.000, 1 <= p <= 500.000), 分别表示玩具的总数,地板上玩具的最多个数以及Jasio 他想玩玩具的序列的个数,接下来p行每行描述一个玩具编号表示Jasio 想玩的玩具.

输出格式:
一个数表示Jasio 的妈妈最少要拿多少次玩具.

输入输出样例

输入样例#1:

3 2 7
1
2
3
1
3
1
2

输出样例#1:

4

题解

贪心的结论是,放回去的一定是此时地上的“下一次使用的时间最晚的”玩具,这个信息可以在读入的时候用链表处理出来。因为当一个玩具还在地上的时候,从此时到下次使用它都不会再满足条件,反而还会占用一个位置,显然把这个位置让给现在用的玩具是最优的。
我们可以维护一个大根堆,按照每个玩具下一次被使用的时间来排序。但是注意,当一个玩具已经在地板上的时候,我们是无法更新这个玩具在堆中的下一次使用时间的信息的,因此我们得往堆中插入新的信息。此时可以用一个很取巧的办法,即对k加1,因为旧信息在以后永远都不会被移除,我们又无法改变堆的size值来修正有效信息的个数,就可以扩大k的值了。
复杂度O(n \log n)

代码

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

#include <algorithm>
#include <queue>
#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();
    while(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 500005;

bool inque[MAXN];

int n, k, p, a[MAXN], nxt[MAXN], head[MAXN];

struct cmp {
    inline bool operator()(const int &a, const int &b) {
        return nxt[a] < nxt[b];
    }
};

std::priority_queue<int, std::vector<int>, cmp> pq;

int main() {
    n = readint(); k = readint(); p = readint();
    for(int i = 1; i <= p; i++) {
        a[i] = readint();
        nxt[head[a[i]]] = i; head[a[i]] = i;
    }
    for(int i = 1; i <= n; i++) {
        nxt[head[i]] = 1e9;
    }
    int ans = 0;
    for(int i = 1; i <= p; i++) {
        if(!inque[a[i]]) {
            if(pq.size() == k) {
                inque[a[pq.top()]] = false; pq.pop();
            }
            inque[a[i]] = true; ans++;
        } else {
            k++;
        }
        pq.push(i);
    }
    printf("%d", ans);
    return 0;
}
[APIO2015]巴厘岛的雕塑 题解

[APIO2015]巴厘岛的雕塑 题解

题目地址:洛谷:【P3646】[APIO2015]巴厘岛的雕塑 – 洛谷、BZOJ:Problem 4069. — [Apio2015]巴厘岛的雕塑

题目描述

印尼巴厘岛的公路上有许多的雕塑,我们来关注它的一条主干道。
在这条主干道上一共有 N 座雕塑,为方便起见,我们把这些雕塑从 1 到 N 连续地进行标号,其中第 i 座雕塑的年龄是 Yi 年。为了使这条路的环境更加优美,政府想把这些雕塑分成若干组,并通过在组与组之间种上一些树,来吸引更多的游客来巴厘岛。
下面是将雕塑分组的规则:
这些雕塑必须被分为恰好 X 组,其中 A< = X< = B,每组必须含有至少一个雕塑,每个雕塑也必须属于且只属于一个组。同一组中的所有雕塑必须位于这条路的连续一段上。
当雕塑被分好组后,对于每个组,我们首先计算出该组所有雕塑的年龄和。
计算所有年龄和按位取或的结果。我们这个值把称为这一分组的最终优美度。
请问政府能得到的最小的最终优美度是多少?

输入输出格式

输入格式:
输入的第一行包含三个用空格分开的整数 N,A,B。
第二行包含 N 个用空格分开的整数 Y1,Y2,…,YN。

输出格式:
输出一行一个数,表示最小的最终优美度。

输入输出样例

输入样例#1:

6 1 3
8 1 2 1 5 4

输出样例#1:

11

说明

子任务 1 (9 分),1< = N< = 20,1< = A< = B< = N,0< = Yi< = 1000000000
子任务 2 (16 分),1< = N< = 50,1< = A< = B< = min{20,N},0< = Yi< = 10
子任务 3 (21 分),1< = N< = 100,A=1,1< = B< = N,0< = Yi< = 20
子任务 4 (25 分),1< = N< = 100,1< = A< = B< = N,0< = Yi< = 1000000000
子任务 5 (29 分),1< = N< = 2000,A=1,1< = B< = N,0< = Yi< = 1000000000

题解

首先既然是最小,又涉及位运算,应该是对二进制位进行DP没跑了。我们从高位往低位来做DP。
其实要解决的问题是“这一位上能不能填0”,那么我们设计DP状态dp[i][j]表示前i个划分成j段,这一位之前的高位与目前最优解相同,这一位能不能填0。枚举k,从dp[k][j – 1]这个状态来进行转移,如果j~i这一段的和能确保目前最优解(即((sum >> pos) | ans) == ans)且这种情况下当前位能填0(即!(sum & (1ll << (pos - 1)))),那么可以转移。
每一位的DP结束后,我们从dp[n][A]到dp[n][B]找是否有DP状态表示解可行,有的话这一位就可以为0,否则必须为1。
那么我们可以知道这个的复杂度是O(n^3 \log (\sum y_i))的,对于子任务5的n范围有些吃紧。
接下来,为了能过子任务5,我们得有一点面向子任务编程的想法。子任务5的特点是A=1,也就是说上面所说的下界限制是没有了的。那么我们考虑直接降低DP状态维度来优化复杂度。我们让dp[i]表示前i个符合目前最优解且最后一位能填0最少的划分段数。我们可以枚举k,从dp[k]像上面类似地往后转移,最后判断的依据是dp[n]是否超过B,超过这一位就必须得为1了。因为不需要枚举j了,复杂度降为O(n^2 \log (\sum y_i))

代码

// Code by KSkun, 2018/3
#include <cstdio>
#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;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 2005;

int n, a, b;
LL y[MAXN];
int f[MAXN][MAXN], g[MAXN];

int main() {
    n = readint();
    a = readint();
    b = readint();
    for(int i = 1; i <= n; i++) {
        y[i] = y[i - 1] + readint();
    }
    int len = 0;
    LL t = y[n];
    while(t) {
        len++;
        t >>= 1;
    }
    LL ans = 0;
    if(a == 1) {
        for(int pos = len; pos; pos--) {
            memset(g, 0x3f, sizeof(g));
            g[0] = 0;
            for(int i = 1; i <= n; i++) {
                for(int j = 0; j < i; j++) {
                    if(g[j] < b) {
                        LL sum = y[i] - y[j];
                        if(((sum >> pos) | ans) == ans && !(sum & (1ll << (pos - 1)))) {
                            g[i] = std::min(g[i], g[j] + 1);
                        }
                    }
                }
            }
            ans <<= 1;
            if(g[n] > b) ans++;
        }
    } else {
        for(int pos = len; pos; pos--) {
            memset(f, 0, sizeof(f));
            f[0][0] = 1;
            for(int i = 1; i <= n; i++) {
                for(int j = 1; j <= i; j++) {
                    for(int k = 0; k < i; k++) {
                        if(f[k][j - 1]) {
                            LL sum = y[i] - y[k];
                            if(((sum >> pos) | ans) == ans && !(sum & (1ll << (pos - 1)))) {
                                f[i][j] = 1;
                            }
                        }
                    }
                }
            }
            ans <<= 1;
            bool success = false;
            for(int i = a; i <= b; i++) {
                if(f[n][i]) {
                    success = true;
                    break;
                }
            }
            if(!success) ans++;
        }
    }
    printf("%lld", ans);
    return 0;
}
Codeforces Round #462 (Div. 2) 题解

Codeforces Round #462 (Div. 2) 题解

比赛地址:Dashboard – Codeforces Round #462 (Div. 2) – Codeforces,也可以在底下的赛题列表点进题目。

赛题列表

#A 934A – A Compatible Pair | 暴力,枚举
#B 934B – A Prosperous Lot | 贪心
#C 933A – A Twisty Movement | 枚举
#D 933B – A Determined Cleanup | 数学
#E 933C – A Colourful Prospect | 计算几何

934A A Compatible Pair

题目描述

给两个数列a, b长分别为n, m,其中a数列要删去一个数,求一种删数的方案,使得所有a_i \cdot b_j中的最大值最小。只需要输出这个最小值。
数据范围:2 \leq n, m \leq 50

样例

样例1

输入:

2 2
20 18
2 14

输出:

252

说明:
删去20,此时最大值为18*14=252。

样例2

输入:

5 3
-1 0 1 2 3
-1 0 1

输出:

2

说明:
删去3,最大值为2*1=2。

题解

枚举删去的数,然后枚举求出此时最大值,维护全部情况的最大值的最小值即可。
复杂度:O(n^3)

代码

// Code by KSkun, 2018/2
#include <cstdio>
#include <algorithm>

int n, m;
long long ans = 1e18 + 10;
long long a[55], b[55];

int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
    }
    for(int i = 1; i <= m; i++) {
        scanf("%lld", &b[i]);
    }
    for(int k = 1; k <= n; k++) {
        long long mx = -1e18 - 10;
        for(int i = 1; i <= n; i++) {
            if(i == k) continue;
            for(int j = 1; j <= m; j++) {
                mx = std::max(mx, a[i] * b[j]);
            }
        }
        ans = std::min(ans, mx);
    }
    printf("%lld", ans);
    return 0;
}

934B A Prosperous Lot

题目描述

定义一个数的圈的个数为这个数中包含数码的圈的个数和,其中,数码的圈的个数依照下图计算。
180214 1 - Codeforces Round #462 (Div. 2) 题解
例如,8的圈的个数为2,而5的圈的个数为0。
给定一个数k,求任意一个不大于10^{18}的非负整数使得其圈的个数为k。如果不存在,输出-1
数据范围:1 \leq k \leq 10^6

样例

样例1

输入:

2

输出:

462

样例2

输入:

6

输出:

8080

题解

我们知道8有两个圈,而6有一个圈。先贪心一下,全用8,如果还剩下1就用1个6,加一起长度超没超18。超了就无解,没超就输出。
复杂度:O(1)

代码

// Code by KSkun, 2018/2
#include <cstdio>

int k, c2, c1;

int main() {
    scanf("%d", &k);
    c2 = k / 2;
    c1 = k % 2;
    if(c1 + c2 > 18) {
        printf("-1");
        return 0;
    } 
    if(c1) putchar('6');
    for(int i = 1; i <= c2; i++) putchar('8');
    return 0;
}

933A A Twisty Movement

题目描述

给定一个只由12构成的数列,长为n,有一次机会翻转任意区间[l, r],求一种翻转方法,使得翻转后的序列的LIS最长。只需要输出最长的LIS长度。
数据范围:1 \leq n \leq 2000

样例

样例1

输入:

4
1 2 1 2

输出:

4

说明:
翻转区间[2, 3]得到序列1 1 2 2

样例2

输入:

10
1 1 2 2 2 1 1 2 2 1

输出:

9

说明:翻转区间[3, 7]得到序列1 1 1 1 2 2 2 2 2 1

题解

这个题需要一些思考。
首先传统的LIS求法是O(n \log n)的,但是12序列可以做到O(n)的。枚举LIS中1和2的分界点,即为该点前1的个数加该点后2的个数。可以分别预处理出来。
我们考虑枚举分界点,然后枚举翻转的区间。可以发现,如果分界点不在区间内部,翻转区间是没有用的。所以区间应该包含这个分界点。翻转后的答案应该是1~区间左端点1的个数+区间左端点~分界点2的个数+分界点~区间右端点1的个数+区间右端点~n 2的个数。左端点和右端点的计算是分开的,所以可以分开枚举分别求最大值,加起来就是一个完整的最大值。
复杂度:O(n^2)

代码

// Code by KSkun, 2018/2
#include <cstdio>
#include <algorithm>

int n, a[2005], c1[2005], c2[2005], ans = 0;

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        c1[i] = c1[i - 1];
        c2[i] = c2[i - 1];
        if(a[i] == 1) {
            c1[i]++;
        } else {
            c2[i]++;
        }
    }
    for(int i = 0; i <= n; i++) {
        ans = std::max(ans, c1[i] + c2[n] - c2[i]);
    }
    for(int i = 0; i <= n; i++) {
        int mx1 = 0, mx2 = 0;
        for(int l = 1; l < i; l++) {
            mx1 = std::max(mx1, c1[l - 1] + c2[i - 1] - c2[l - 1]);
        }
        for(int r = i; r <= n; r++) {
            mx2 = std::max(mx2, c1[r] - c1[i - 1] + c2[n] - c2[r]);
        }
        ans = std::max(ans, mx1 + mx2);
    }
    printf("%d", ans);
    return 0;
}

933B A Determined Cleanup

题目描述

给两个数p和k,要求求一个多项式f(x),使得f(x)满足f(x)=q(x) \cdot (x+k)+p,其中q(x)是另一个多项式,且f(x)各项的系数和常数项都不大于k。如果无解,输出-1
数据范围:1 \leq p \leq 10^{18}, 2 \leq k \leq 2000

样例

样例1

输入:

46 2

输出:

7
0 1 0 0 1 1 1

说明:
f(x) = x^6 + x^5 + x^4 + x = (x^5 - x^4 + 3x^3 - 6x^2 + 12x - 23)(x + 2) + 46

样例2

输入:

2018 214

输出:

3
92 205 1

说明:
f(x) = x^2 + 205x + 92 = (x - 9)(x + 214) + 2018

题解

本题需要用到余式定理。余式定理的内容是多项式f(x)除以多项式(x – a)的余式为f(a)。
余式定理的证明可以参考余式定理_百度百科
这里需要f(x)除以(x + k)余p,就是说f(-k) = p。那么我们只需要将p化成一个-k进制数即可。这样看来,本题不存在无解的情况。
复杂度:O(\log p)

代码

// Code by KSkun, 2018/2
#include <cstdio>
#include <vector>

long long p;
int k;

std::vector<int> vec;

int main() {
    scanf("%lld%d", &p, &k);
    while(p != 0) {
        vec.push_back((p % k + k) % k);
        p = (vec.back() - p) / k;
    }
    printf("%d\n", vec.size());
    for(int i = 0; i < vec.size(); i++) {
        printf("%d ", vec[i]);
    } 
    return 0;
} 

933C A Colourful Prospect

题目描述

给n个圆,求n个圆将平面分成几个区域(内部不包含任何弧线)。
数据范围:1 \leq n \leq 3

样例

样例1

输入:

3
0 0 1
2 0 1
4 0 1

输出:

4

说明:
180214 2 - Codeforces Round #462 (Div. 2) 题解

样例2

输入:

3
0 0 2
3 0 2
6 0 2

输出:

6

说明:
180214 3 - Codeforces Round #462 (Div. 2) 题解

样例3

输入:

3
0 0 2
2 0 2
1 1 2

输出:

8

说明:
180214 4 - Codeforces Round #462 (Div. 2) 题解

题解

需要使用欧拉定理(V-E+F=2)解决。暂未解决。
复杂度: