AIM Tech Round 5 (rated, Div. 1 + Div. 2)
A - Find Square
题意:给一个n*m的矩阵全是字符'W',其中一块奇数边长的正方形变成了'B',找出其中心。
题解:逐个扫描可以找到右下角,然后首次找到的是左上角。取平均。
没意思。
int n, m;
char g[205][205];
void test_case() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i)
scanf("%s", g[i] + 1);
int l1 = -1, l2 = -1;
int s1 = -1, s2 = -1;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
if(g[i][j] == 'B') {
l1 = i;
l2 = j;
if(s1 == -1)
s1 = i;
if(s2 == -1)
s2 = j;
}
}
}
printf("%d %d\n", (s1 + l1) / 2, (s2 + l2) / 2);
}
B - Unnatural Conditions
题意:记s(x)为x的十进制各位之和,给定n和m(>=1),要求构造两个a,b,满足:
s(a)>=n
s(b)>=n
s(a+b)<=m
a,b长度不能超过2230位。
构造一种进位之后恰好最高位是1的做法就可以了。然后尽可能长。
int n, m;
void test_case() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= 2200; ++i)
putchar('5');
putchar('5');
putchar('\n');
for(int i = 1; i <= 2200; ++i)
putchar('4');
putchar('5');
putchar('\n');
}
C - Rectangles
题意:给n个1e9范围内的矩形,求一个点被至少n-1个矩形覆盖。多解输出其中任意一个。
题解:无解输出什么?一种简单的想法是扫描线。从下到上扫描,记录每个矩形的入口和出口,在线段树上面区间+1,当某个点被覆盖超过n-1次时输出这个点。想了另一种假做法:与某点不相交的矩形,要么完全在左边,要么完全在右边。另一方面要么完全在上边,要么完全在下边。这种解法把x和y分开看导致错误。
假算法:
int n;
int x1[150005], y1[150005];
int x2[150005], y2[150005];
int xx[300005], yy[300005], top, xtop, ytop;
int dx[300005], dy[300005];
int dx2[300005], dy2[300005];
bool check(int x, int y) {
printf("x=%d y=%d\n", x, y);
int LX = dx2[x - 1];
int RX = n - dx[x];
if(LX + RX >= 2)
return false;
int LY = dy2[y - 1];
int RY = n - dy[y];
if(LY + RY >= 2)
return false;
printf("LX=%d RX=%d LY=%d RY=%d\n", LX, RX, LY, RY);
return true;
}
void test_case() {
scanf("%d", &n);
top = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d%d%d%d", &x1[i], &y1[i], &x2[i], &y2[i]);
++top;
xx[top] = x1[i];
yy[top] = y1[i];
++top;
xx[top] = x2[i];
yy[top] = y2[i];
}
sort(xx + 1, xx + 1 + top);
xtop = unique(xx + 1, xx + 1 + top) - (xx + 1);
sort(yy + 1, yy + 1 + top);
ytop = unique(yy + 1, yy + 1 + top) - (yy + 1);
for(int i = 1; i <= n; ++i) {
x1[i] = lower_bound(xx + 1, xx + 1 + xtop, x1[i]) - (xx);
x2[i] = lower_bound(xx + 1, xx + 1 + xtop, x2[i]) - (xx);
y1[i] = lower_bound(yy + 1, yy + 1 + ytop, y1[i]) - (yy);
y2[i] = lower_bound(yy + 1, yy + 1 + ytop, y2[i]) - (yy);
printf("%d %d %d %d\n", x1[i], y1[i], x2[i], y2[i]);
}
for(int i = 1; i <= n; ++i) {
++dx[x1[i]];
++dx2[x2[i]];
++dy[y1[i]];
++dy2[y2[i]];
}
for(int i = 1; i <= xtop; ++i) {
dx[i] += dx[i - 1];
dx2[i] += dx2[i - 1];
}
for(int i = 1; i <= ytop; ++i) {
dy[i] += dy[i - 1];
dy2[i] += dy2[i - 1];
}
for(int i = 1; i <= n; ++i) {
if(check(x1[i], y1[i])) {
printf("%d %d\n", xx[x1[i]], yy[y1[i]]);
return;
}
if(check(x1[i], y2[i])) {
printf("%d %d\n", xx[x1[i]], yy[y2[i]]);
return;
}
if(check(x2[i], y1[i])) {
printf("%d %d\n", xx[x2[i]], yy[y1[i]]);
return;
}
if(check(x2[i], y2[i])) {
printf("%d %d\n", xx[x2[i]], yy[y2[i]]);
return;
}
}
}
x和y是不能分开考虑的,过不了第一个样例。
正确做法:扫描线。注意这里有一些需要思考的地方,这里的出口只是应该在y的坐标输入进来的时候+1,而不能在离散化之后+1,x是不需要出口的,因为闭区间写法的线段树不是差分。找到最大值之后可以顺便返回最大值对应的x的位置。
int n;
int x1[150005], y1[150005];
int x2[150005], y2[150005];
int xx[300005], yy[300005], top, xtop, ytop;
vector<pii> upd[300005];
struct SegmentTree {
#define ls (o<<1)
#define rs (o<<1|1)
static const int MAXN = 300000;
static const int INF = 0x3f3f3f3f;
int ma[(MAXN << 2) + 5];
int ps[(MAXN << 2) + 5];
int lz[(MAXN << 2) + 5];
void PushUp(int o) {
if(ma[ls] >= ma[rs]) {
ma[o] = ma[ls];
ps[o] = ps[ls];
} else {
ma[o] = ma[rs];
ps[o] = ps[rs];
}
}
void PushDown(int o, int l, int r) {
if(lz[o]) {
lz[ls] += lz[o];
lz[rs] += lz[o];
ma[ls] += lz[o];
ma[rs] += lz[o];
lz[o] = 0;
}
}
void Build(int o, int l, int r) {
if(l == r) {
ma[o] = 0;
ps[o] = l;
} else {
int m = l + r >> 1;
Build(ls, l, m);
Build(rs, m + 1, r);
PushUp(o);
}
lz[o] = 0;
}
void Update(int o, int l, int r, int ql, int qr, int v) {
if(ql <= l && r <= qr) {
lz[o] += v;
ma[o] += v;
} else {
PushDown(o, l, r);
int m = l + r >> 1;
if(ql <= m)
Update(ls, l, m, ql, qr, v);
if(qr >= m + 1)
Update(rs, m + 1, r, ql, qr, v);
PushUp(o);
}
}
pii QueryMax(int o, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) {
return {ma[o], ps[o]};
} else {
PushDown(o, l, r);
int m = l + r >> 1;
pii res = {-INF, -1};
if(ql <= m)
res = QueryMax(ls, l, m, ql, qr);
if(qr >= m + 1) {
pii tmp = QueryMax(rs, m + 1, r, ql, qr);
if(tmp.first > res.first)
res = tmp;
}
return res;
}
}
#undef ls
#undef rs
} st;
void test_case() {
scanf("%d", &n);
top = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d%d%d%d", &x1[i], &y1[i], &x2[i], &y2[i]);
++y2[i];
++top;
xx[top] = x1[i];
yy[top] = y1[i];
++top;
xx[top] = x2[i];
yy[top] = y2[i];
}
sort(xx + 1, xx + 1 + top);
xtop = unique(xx + 1, xx + 1 + top) - (xx + 1);
sort(yy + 1, yy + 1 + top);
ytop = unique(yy + 1, yy + 1 + top) - (yy + 1);
for(int i = 1; i <= n; ++i) {
x1[i] = lower_bound(xx + 1, xx + 1 + xtop, x1[i]) - (xx);
x2[i] = lower_bound(xx + 1, xx + 1 + xtop, x2[i]) - (xx);
y1[i] = lower_bound(yy + 1, yy + 1 + ytop, y1[i]) - (yy);
y2[i] = lower_bound(yy + 1, yy + 1 + ytop, y2[i]) - (yy);
//printf("%d %d %d %d\n", x1[i], y1[i], x2[i], y2[i]);
upd[y1[i]].push_back({x1[i], x2[i]});
upd[y2[i]].push_back({-x1[i], -x2[i]});
}
st.Build(1, 1, xtop);
for(int i = 1; i <= ytop; ++i) {
for(int j = 0; j < upd[i].size(); ++j) {
int l = upd[i][j].first;
int r = upd[i][j].second;
if(l > 0 && r > 0)
st.Update(1, 1, xtop, l, r, 1);
else
st.Update(1, 1, xtop, -l, -r, -1);
}
pii res = st.QueryMax(1, 1, xtop, 1, xtop);
if(res.first >= n - 1) {
int X = res.second;
int Y = i;
printf("%d %d\n", xx[X], yy[Y]);
return;
}
}
printf("-1 -1\n");
exit(-1);
}
更快的做法:注意到n-1个矩形的交也是一个矩形(可能为空)。取矩形的前缀交,和后缀交,那么假如把第i个矩形排除,则pre[i-1]和suf[i+1]的交中的任何一点都是解。这个是这个问题是n-1的一种特殊解法。假如n-2就没辙了。还是扫描线比较高级又通用。
不过我当时比赛的时候是怎么会知道题解这种做法的呢?可能因为我当时不会扫描线吧。几乎全部人都是用题解的解法的,包括当时的我。
那么求矩形的交很简单,取最右的左边界,最上的下边界,最左的右边界,最下的上边界就可以了。