Codeforces Global Round 5
https://codeforces.com/contest/1237
A - Balanced Rating Changes
题意:给一堆和为0的数字,然后偶数直接除以2输出,奇数就除以2之后均匀分配上下整使得和依然是0。
题解:奇数全部取下整,然后间隔着加。注意C++里面的整数除法是向0取整,假如全部除以2的话应该是错的,可以构造这样的:
4
-3
-5
4
4
这样两个奇数都是负的,然后就多了。
void test_case() {
int n;
scanf("%d", &n);
int a, b = 0;
while(n--) {
scanf("%d", &a);
if(a & 1) {
++b;
int t = floor(a / 2.0);
t += (b & 1);
printf("%d\n", t);
} else
printf("%d\n", a / 2);
}
}
不想用浮点数的话就分正负判断吧,但是没必要,在这个4位整数的位数上面浮点数几乎没有误差。
B - Balanced Tunnel
题意:给一个入隧道的序列,一个出隧道的序列,判断谁在隧道里面超车。
题解:先把题目给的反序变成从左到右看的顺序。首先最右边的车是不可能超车的。然后其他车假如他右侧的车的出隧道后的最左值在他左边,那么他一定进行了超车。
int a[100005];
int aa[100005];
int b[100005];
int bb[100005];
int leftmost[100005];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
for(int i = 1; i <= n; ++i)
scanf("%d", &b[i]);
reverse(a + 1, a + 1 + n);
reverse(b + 1, b + 1 + n);
for(int i = 1; i <= n; ++i) {
aa[a[i]] = i;
bb[b[i]] = i;
}
leftmost[n] = bb[a[n]];
for(int i = n - 1; i >= 1; --i)
leftmost[i] = min(leftmost[i + 1], bb[a[i]]);
int cnt = 0;
for(int i = 1; i <= n - 1; ++i) {
if(leftmost[i + 1] < bb[a[i]])
++cnt;
}
printf("%d\n", cnt);
}
C2 - Balanced Removals (Harder)
题意:给n个三维点,n是偶数。每次消去一对,要求每次消除的时候这两个点包围的矩形盒内没有其他点。
题解:先按z分层,然后同一层按y分条,同一条按x排序,x相邻的两两之间没有其他点全部消去,多余的至多一个点传给上层。然后接下来的y相邻的之间的矩形也不会有其他点,两两消去。
struct Point1 {
int x, id;
bool operator<(const Point1& p)const {
return x < p.x;
}
} p1[50005];
struct Point2 {
int x, y, id;
bool operator<(const Point2& p)const {
return y < p.y;
}
} p2[50005];
struct Point3 {
int x, y, z, id;
bool operator<(const Point3& p)const {
return z < p.z;
}
} p3[50005];
int solve_y(int L, int R) {
int n = R - L + 1;
for(int i = 1; i <= n; ++i) {
p1[i].x = p2[L + i - 1].x;
p1[i].id = p2[L + i - 1].id;
}
sort(p1 + 1, p1 + 1 + n);
for(int i = 1; i + 1 <= n; i += 2)
printf("%d %d\n", p1[i].id, p1[i + 1].id);
if(n & 1)
return p1[n].id;
return 0;
}
int solve_z(int L, int R) {
int n = R - L + 1;
for(int i = 1; i <= n; ++i) {
p2[i].x = p3[L + i - 1].x;
p2[i].y = p3[L + i - 1].y;
p2[i].id = p3[L + i - 1].id;
}
sort(p2 + 1, p2 + 1 + n);
queue<int> Q;
int l = 1, r = 1;
while(l <= n) {
while(r + 1 <= n && p2[r + 1].y == p2[l].y)
++r;
int p = solve_y(l, r);
if(p) {
Q.push(p);
if(Q.size() == 2) {
printf("%d", Q.front());
Q.pop();
printf(" %d\n", Q.front());
Q.pop();
}
}
l = r + 1;
r = l;
}
if(Q.size())
return Q.front();
return 0;
}
int n;
void solve() {
sort(p3 + 1, p3 + 1 + n);
int l = 1, r = 1;
queue<int> Q;
while(l <= n) {
while(r + 1 <= n && p3[r + 1].z == p3[l].z)
++r;
int p = solve_z(l, r);
if(p) {
Q.push(p);
if(Q.size() == 2) {
printf("%d", Q.front());
Q.pop();
printf(" %d\n", Q.front());
Q.pop();
}
}
l = r + 1;
r = l;
}
}
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d%d%d", &p3[i].x, &p3[i].y, &p3[i].z);
p3[i].id = i;
}
solve();
}
D - Balanced Playlist
题意:给一个音乐序列,循环播放,对于每个位置i,计算以i为起始时会听多少音乐。当你听到一首严格小于你听过的歌的cool度的最大值的一半的时候就会停止,或者报告不会停止。
题解:先观察,发现从全局最小开始听可以一直模拟出能听的最大值,一直搜索过去直到碰到底再一个一个回溯上来,或者报告听了一圈,假如听了一圈“成功”回到全局最小就说明可以一直听下去。否则回溯的时候就会把中间的所有的都更新?这样是错的,考虑下面的:
5 3 11 6
从5开始听到6就结束了,但是从6开始听到5才结束。注意到好像可以按非严格上升的序列来分组,同一组的肯定是贡献同一个结束位置的。而组与组之间有可能连上,一旦连上就会整组连上。和之前做的一场很像。但是怎么转移呢?
int n;
int a[200005], b[200005];
int l[200005], r[200005], cnt;
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
a[i + n] = a[i];
}
a[2 * n + 1] = a[1];
int maxai = 0, minai = INF;
for(int i = 1; i <= n; ++i) {
if(a[i] < minai)
minai = a[i];
if(a[i] > maxai)
maxai = a[i];
}
if(maxai <= 2 * minai) {
for(int i = 1; i <= n; ++i)
printf("%d%c", -1, " \n"[i == n]);
return;
}
int beg = 1;
while(a[beg] <= a[beg + 1])
++beg;
if(a[beg] > a[beg + 1])
++beg;
for(int i = beg, u = 1; u <= n; ++i, ++u)
printf("%d%c", a[i], " \n"[u == n]);
cnt = 0;
int cur = beg;
while(cur - beg + 1 <= n) {
l[++cnt] = cur, r[cnt] = cur;
while(a[r[cnt]] <= a[r[cnt] + 1])
++r[cnt];
for(int i = l[cnt]; i <= r[cnt]; ++i)
b[i] = cnt;
cur = r[cnt] + 1;
}
for(int i = beg, u = 1; u <= n; ++i, ++u)
printf("%d%c", b[i], " \n"[u == n]);
}
组划分好了,从全局最高的开始向右一直找,找到第一个小于它的一半的,经过上面的筛选可知这样的数一定存在,那么这个全局最高的值就确定了。然后把它的整个组全部从右往左依次更新。更新完这组后,尝试打通这一组和上一组的联系。
但是好复杂耶!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
#define rep(i,p1,p2) for(int i=(p1);i<=(p2);++i)
#define per(i,p1,p2) for(int i=(p1);i>=(p2);--i)
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
int qpow(ll x, ll n) {
ll res = 1;
while(n) {
if(n & 1)
res = res * x % MOD;
x = x * x % MOD;
n >>= 1;
}
return res;
}
const int MAXN = 2e5;
/*---*/
int n;
int a[200005], b[200005], c[200005];
int l[200005], r[200005], cnt;
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
a[i + n] = a[i];
}
a[2 * n + 1] = a[1];
int maxai = 0, minai = INF;
for(int i = 1; i <= n; ++i) {
if(a[i] < minai)
minai = a[i];
if(a[i] > maxai)
maxai = a[i];
}
if(maxai <= 2 * minai) {
for(int i = 1; i <= n; ++i)
printf("%d%c", -1, " \n"[i == n]);
return;
}
int beg = 1;
while(a[beg] <= a[beg + 1])
++beg;
if(a[beg] > a[beg + 1])
++beg;
for(int i = beg, u = 1; u <= n; ++i, ++u)
printf("%d%c", a[i], " \n"[u == n]);
cnt = 0;
int cur = beg;
while(cur - beg + 1 <= n) {
l[++cnt] = cur, r[cnt] = cur;
while(a[r[cnt]] <= a[r[cnt] + 1])
++r[cnt];
for(int i = l[cnt]; i <= r[cnt]; ++i)
b[i] = cnt;
cur = r[cnt] + 1;
}
for(int i = beg, u = 1; u <= n; ++i, ++u) {
if(i > n)
b[i - n] = b[i];
if(i + n <= 2 * n)
b[i + n] = b[i];
printf("%d%c", b[i], " \n"[u == n]);
}
int maxaii = beg;
for(; a[maxaii] != maxai; ++maxaii)
;
{
int cnt = 1;
for(int i = maxaii + 1; maxai <= 2 * a[i]; ++i)
++cnt;
c[maxaii] = cnt;
int cur = maxaii - 1, head = maxaii, rest = n - 1, curmin = maxaii;
while(rest) {
while(rest && b[cur] == b[head]) {
c[cur] = ++cnt;
curmin = min(curmin, a[cur]);
--cur;
if(cur == 0)
cur = n;
--rest;
}
if(rest) {
if(a[cur] <= 2 * curmin) {
c[cur] = ++cnt;
curmin = min(curmin, a[cur]);
head = cur;
--cur;
if(cur == 0)
cur = n;
--rest;
} else {
c[cur] = cnt = 1;
curmin = a[cur];
head = cur;
--cur;
if(cur == 0)
cur = n;
--rest;
}
}
}
}
for(int i = maxaii; i > n; --i)
c[i - n] = c[i];
for(int i = 1; i <= n; ++i)
printf("%d%c", c[i], " \n"[i == n]);
}
10
4 3 6 4 2 4 2 4 1 9
O(n)的做法:再看一下觉得像双指针,从任意一个位置开始向右扩展,到极限之后不断向左更新,当无法再向左更新时,说明右端点实在太小了,这个时候就把右端点吐出去,直到区间中的最小值大于等于左端点的一半的下整。数据结构,就弄个双栈队列查最小值就可以了。
int n, a[100005], b[100005];
struct Queue {
static const int MAXN = 1000000;
static const int INF = 1061109567;
int s1[MAXN + 5], s2[MAXN + 5];
int s1top, s2top, s1min;
void Clear() {
s1top = 0;
s2top = 0;
s2[0] = INF;
s1min = INF;
}
void Push(int x) {
s1[++s1top] = x;
s1min = min(s1min, x);
}
void Pop() {
if(s2top)
--s2top;
else {
while(s1top)
s2[++s2top] = min(s2[s2top - 1], s1[s1top--]);
--s2top;
s1min = INF;
}
}
int Size() {
return s1top + s2top;
}
int Min() {
return min(s2[s2top], s1min);
}
} Q;
void test_case() {
Q.Clear();
scanf("%d", &n);
int maxi = 1, mini = 1;
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
if(a[i] > a[maxi])
maxi = i;
if(a[i] < a[mini])
mini = i;
}
a[n + 1] = a[1];
if(a[maxi] <= 2 * a[mini]) {
for(int i = 1; i <= n; ++i)
printf("%d%c", -1, " \n"[i == n]);
return;
}
int cur = maxi;
while(a[maxi] <= 2 * a[cur]) {
++cur;
if(cur == n + 1)
cur = 1;
}
--cur;
if(cur == 0)
cur = n;
while(cur != maxi) {
Q.Push(a[cur]);
--cur;
if(cur == 0)
cur = n;
};
int rest = n;
while(rest) {
while(a[cur] > 2 * Q.Min())
Q.Pop();
Q.Push(a[cur]);
b[cur] = Q.Size();
--cur;
if(cur == 0)
cur = n;
--rest;
}
for(int i = 1; i <= n; ++i)
printf("%d%c", b[i], " \n"[i == n]);
return;
}
假算法:假如是变成RMQ,就是对每个起点作为左端点,找一个最长的k,使得这个区间的最大值<=2*这个区间的最小值,变成一个二分然后在ST表上面找的问题,这个实现最简单,写这个。(这个做法好假啊,写出来才发现)