Codeforces Round #577 (Div. 2)
Posted kisekipurin2019
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Codeforces Round #577 (Div. 2)相关的知识,希望对你有一定的参考价值。
题目链接:https://codeforces.com/contest/1201
A - Important Exam
送分题。
B - Zero Array
题意:给一个数组,每次选两个不同位置的数字,然后同时-1。问是否能全部变成0。
题解:好像做过。首先和必须是偶数,其次最大的数字不能超过和的一半,这两个显然是必要条件,但是为什么这样就充分了呢?貌似可以归纳,每次取最大和次大同时-1,问题会变成和-2的,且最大的数字依然不超过和的一半的子问题,因为新的最大值要么是原本的最大值-1,要么原本有至少3个值都是原本的最大值,两种情况下新的最大值都不会超过和的一半。即:已知sum>=3x,求证x<=(sum-2)/2。显然当sum>=6时上式成立,当sum<6时,枚举掉这种情况即可。
void test_case() {
int n;
scanf("%d", &n);
ll sum = 0, maxa = 0;
while(n--) {
ll x;
scanf("%lld", &x);
sum += x;
maxa = max(maxa, x);
}
if(sum % 2 == 1 || maxa > sum / 2)
puts("NO");
else
puts("YES");
}
C - Maximum Median
题意:给一个奇数个数的数组,每次操作可以把一个数字+1,求最多k次操作能变到的最大的中位数。
题解:很明显有二分的做法:枚举中位数x,需要把中间及其之后的所有小于x的数变成x,求出这个花费是否超过k,求花费的时候还要再进行一次二分,带两个log。
int n, k, mid;
int a[200005];
ll sum[200005];
bool check(int x) {
int pos = lower_bound(a + 1, a + 1 + n, x) - a;
--pos;
if(pos < mid)
return 1;
int cnt = pos - mid + 1;
ll cost = 1ll * (pos - mid + 1) * x - (sum[pos] - sum[mid - 1]);
return cost <= k;
}
void test_case() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++i)
sum[i] = sum[i - 1] + a[i];
mid = (n + 1) / 2;
int L = a[mid], R = a[mid] + k;
while(1) {
int M = (L + R) >> 1;
if(L == M) {
if(L == R || check(R)) {
printf("%d
", R);
return;
}
printf("%d
", L);
return;
}
if(check(M))
L = M;
else
R = M - 1;
}
}
但是居然T了。卡一下常数试试。最后发现是二分的时候加法溢出了。
int n, k, mid;
int a[200005];
ll sum[200005];
bool check(ll x) {
int pos = lower_bound(a + 1, a + 1 + n, x) - a;
--pos;
if(pos < mid)
return 1;
ll cost = 1ll * (pos - mid + 1) * x - (sum[pos] - sum[mid - 1]);
return cost <= k;
}
void test_case() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++i)
sum[i] = sum[i - 1] + a[i];
mid = (n + 1) / 2;
ll L = a[mid], R = a[mid] + k;
while(1) {
ll M = (L + R) >> 1;
if(L == M) {
if(L == R || check(R)) {
printf("%d
", R);
return;
}
printf("%d
", L);
return;
}
if(check(M))
L = M;
else
R = M - 1;
}
}
启示:上了1e9的都用ll比较好,说不定哪天就溢出了。在二分lower_bound的时候起点其实不一定要是1,直接从mid开始也可以,不过也就快了一次?
挑战一下主体部分线性的做法,因为这题自带一个排序,所以本身的一个log是逃不掉的,思路是一次把相等的一段数字全部吃进来,然后求中位数到当前这段数字的和,加上k之后除以这段数字的个数,就得到了中位数的大小tmp。需要注意的是tmp不能超过a[r+1],因为假如超过了就应该把下一段也吃进来,这个tmp也不能小于a[r],否则就说明中位数不能到达a[r](意思是要把a[r]的一部分分给前面的数才使得大家都是tmp,这是不对的)。
int n, k;
ll a[200005];
void test_case() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i)
scanf("%lld", &a[i]);
sort(a + 1, a + 1 + n);
int mid = (n + 1) / 2;
ll sum = 0, ans = a[mid];
for(int l = mid, r; l <= n; l = r + 1) {
for(r = l; r + 1 <= n && a[r + 1] == a[l]; ++r);
for(int j = l; j <= r; ++j)
sum += a[j];
ll tmp = (k + sum) / (r - mid + 1);
if(tmp < a[r])
break;
if(r != n)
tmp = min(a[r + 1], tmp);
ans = max(ans, tmp);
}
printf("%lld
", ans);
}
D - Treasure Hunting
题意:有个n*m的迷宫,从左下角开始走,迷宫中有一些宝物,要用最少的步数收集所有的宝物。每次移动只能向左向右或者向上移动,其中只有在某些安全列才能向上移动。
题解:看起来就很dp,先去掉最高的有宝物的层,那么收集完一层之后,必定从某个安全列去往下一层,那么到达上层的每个安全列的最小步数是可以直接转移出来的。易知搜集完某层的宝物之后,分别从两侧的安全列其中之一去往上层是最优的解法。且每层的最左边或者最右边的宝物是最后被收集的,所以真正有用的安全列只有最多4个。最后特殊处理一下顶层。
以上是关于Codeforces Round #577 (Div. 2)的主要内容,如果未能解决你的问题,请参考以下文章
Codeforces Round #436 E. Fire(背包dp+输出路径)