[JSOI2010]缓存交换 题解
题目地址:ė …
May all the beauty be blessed.
题目地址:洛谷:【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;
}
题目地址:洛谷:【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;
}
比赛地址: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 | 计算几何
给两个数列a, b长分别为n, m,其中a数列要删去一个数,求一种删数的方案,使得所有a_i \cdot b_j中的最大值最小。只需要输出这个最小值。
数据范围:2 \leq n, m \leq 50
输入:
2 2 20 18 2 14
输出:
252
说明:
删去20,此时最大值为18*14=252。
输入:
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;
}
定义一个数的圈的个数为这个数中包含数码的圈的个数和,其中,数码的圈的个数依照下图计算。
例如,8
的圈的个数为2,而5
的圈的个数为0。
给定一个数k,求任意一个不大于10^{18}的非负整数使得其圈的个数为k。如果不存在,输出-1
。
数据范围:1 \leq k \leq 10^6
输入:
2
输出:
462
输入:
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;
}
给定一个只由1
和2
构成的数列,长为n,有一次机会翻转任意区间[l, r],求一种翻转方法,使得翻转后的序列的LIS最长。只需要输出最长的LIS长度。
数据范围:1 \leq n \leq 2000
输入:
4 1 2 1 2
输出:
4
说明:
翻转区间[2, 3]得到序列1 1 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;
}
给两个数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
输入:
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
输入:
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;
}
给n个圆,求n个圆将平面分成几个区域(内部不包含任何弧线)。
数据范围:1 \leq n \leq 3
输入:
3 0 0 1 2 0 1 4 0 1
输出:
4
说明:
输入:
3 0 0 2 3 0 2 6 0 2
输出:
6
说明:
输入:
3 0 0 2 2 0 2 1 1 2
输出:
8
说明:
需要使用欧拉定理(V-E+F=2)解决。暂未解决。
复杂度: