JOISC2020 题解
Day1T1 建筑装饰4
题目链接:Day1T1 建筑装饰4
Solution
我们先考虑朴素的\(dp\)方法:
设\(dp_{i,j,k}\)表示前\(i\)个数中,选了\(j\)个\(B\)数组中的数,并且第\(i\)个数选的是\(A/B\)时,是否存在满足单调不降的数列。
复杂度:\(O(n^2)\),期望得分:\(11\)分。
考虑如何优化。可以打个表,发现当\(i,k\)固定的时候,满足\(dp_{i,j,k}=1\)的\(j\)刚好是一个区间。
我们可以大胆猜结论:如果\(dp_{i,j_1,k}=1\)并且\(dp_{i,j_2,k}=1\),那么对于任意\(j_1\le j\le j_2\),都满足\(dp_{i,j,k}=1\)。
于是,我们可以记录数组\(l_{i,k}\)和\(r_{i,k}\)分别表示最小的\(j\)和最大的\(j\)。
输出路径的时候从后往前找即可。
复杂度:\(O(n)\),期望得分:\(100\)分。
Code
// Author: wlzhouzhuan
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
inline int read() {
int x = 0, neg = 1; char op = getchar();
while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
return neg * x;
}
inline void print(int x) {
if (x < 0) { putchar('-'); x = -x; }
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
const int inf = 1e9;
const int N = 1000005;
int l[N][2], r[N][2];
int a[N][2], n;
void ckmin(int &a, int b) {
if (a > b) a = b;
}
void ckmax(int &a, int b) {
if (a < b) a = b;
}
int main() {
// freopen("building.in", "r", stdin);
// freopen("building.out", "w", stdout);
n = read();
a[0][0] = a[0][1] = -inf, a[2 * n + 1][0] = a[2 * n + 1][1] = inf;
for (rint i = 1; i <= 2 * n; i++) a[i][0] = read();
for (rint i = 1; i <= 2 * n; i++) a[i][1] = read();
for (rint i = 1; i <= 2 * n; i++) {
for (rint j = 0; j <= 1; j++) {
l[i][j] = inf, r[i][j] = -inf;
for (rint k = 0; k <= 1; k++) {
if (a[i][j] >= a[i - 1][k]) {
ckmin(l[i][j], l[i - 1][k] + (j == 0));
ckmax(r[i][j], r[i - 1][k] + (j == 0));
}
}
}
}
stack <int> ans;
if (l[2 * n][0] <= n && n <= r[2 * n][0]) {
int lef = n, st = 0;
ans.push(0);
for (rint i = 2 * n - 1; i >= 1; i--) {
if (st == 0) {
lef--;
for (rint j = 0; j <= 1; j++) {
if (a[i + 1][st] >= a[i][j] && l[i][j] <= lef && lef <= r[i][j]) {
st = j;
break;
}
}
} else {
for (rint j = 0; j <= 1; j++) {
if (a[i + 1][st] >= a[i][j] && l[i][j] <= lef && lef <= r[i][j]) {
st = j;
break;
}
}
}
ans.push(st);
}
while (!ans.empty()) {
if (ans.top() == 0) putchar('A');
else putchar('B');
ans.pop();
}
puts("");
} else if (l[2 * n][1] <= n && n <= r[2 * n][1]) {
int lef = n, st = 1;
ans.push(1);
for (rint i = 2 * n - 1; i >= 1; i--) {
if (st == 0) {
lef--;
for (rint j = 0; j <= 1; j++) {
if (a[i + 1][st] >= a[i][j] && l[i][j] <= lef && lef <= r[i][j]) {
st = j;
break;
}
}
} else {
for (rint j = 0; j <= 1; j++) {
if (a[i + 1][st] >= a[i][j] && l[i][j] <= lef && lef <= r[i][j]) {
st = j;
break;
}
}
}
ans.push(st);
}
while (!ans.empty()) {
if (ans.top() == 0) putchar('A');
else putchar('B');
ans.pop();
}
puts("");
} else {
puts("-1");
}
return 0;
}
Day1T2 汉堡肉
题目链接:Day1T2 汉堡肉
Solution
对于\(k=1\),显然这个点位于这\(n\)个矩形的交。
复杂度:\(O(n)\),期望得分:\(2\)分。
对于\(k=2\),令\(r=min_{i=1}^{n}R_i\),令其中一个点的横坐标为\(r\),同时枚举它所有的可能纵坐标,并且包含这个点的矩形删掉。
剩余的矩形按\(k=1\)的做法即可得到另一个点。
复杂度:\(O(n^2)\),期望得分:\(1\)分,结合\(k=1\)可得\(3\)分。
考虑如何优化。我们先将所有的矩形按照\(R_i\)优化,然后在扫描线的过程中记录剩余矩形中的\(max(L_i),max(D_i),min(R_i),min(U_i)\)。
具体过程可以用优先队列来实现。
复杂度:\(O(nlogn)\),期望得分:\(3\)分,结合\(k=1\)可得\(6\)分。
对于剩下的点,我采用了随机化做法。
先随机打乱这\(n\)个矩形,然后将前\(k\)个矩形分别放置这\(k\)类。
接下来,将第\(k+1到n\)这些矩形依次插入这\(k\)类。
定义估价函数\(h(rec_x,rec_y)=\frac{area(merge(rec_x,rec_y))}{area(rec_y)}\)表示覆盖率。
我们可以去寻找第\(i\)个矩形与第\(j\)类矩阵交后的估价函数最大值。
那么估价函数越高,即越优秀,匹配成功的概率最大。
按照如上方式随机化,可以通过此题。
复杂度:\(O(欧皇)\),期望得分:\(100\)分。
Code
// Author: wlzhouzhuan
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
inline int read() {
int x = 0, neg = 1; char op = getchar();
while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
return neg * x;
}
inline void print(int x) {
if (x < 0) { putchar('-'); x = -x; }
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
const int N = 200005;
int n, k;
struct rect {
ll x1, y1, x2, y2;
rect(ll _x1 = 0, ll _y1 = 0, ll _x2 = 0, ll _y2 = 0) {
x1 = _x1, y1 = _y1, x2 = _x2, y2 = _y2;
}
} a[N], b[5];
rect merge(rect x, rect y) {
return rect(max(x.x1, y.x1), max(x.y1, y.y1), min(x.x2, y.x2), min(x.y2, y.y2));
}
ll area(rect x) { // 矩形内包含多少点
if (x.x1 > x.x2 || x.y1 > x.y2) return 0;
else return (x.x2 - x.x1 + 1) * (x.y2 - x.y1 + 1);
}
double h(rect x, rect y) {
rect m = merge(x, y);
return area(m) / 1.0 / area(y);
}
int main() {
srand(time(NULL));
scanf("%d%d", &n, &k);
for (rint i = 1; i <= n; i++) {
scanf("%lld%lld%lld%lld", &a[i].x1, &a[i].y1, &a[i].x2, &a[i].y2);
}
while (true) {
int ok = 1;
random_shuffle(a + 1, a + n + 1);
for (rint i = 1; i <= k; i++) b[i] = a[i];
for (rint i = k + 1; i <= n; i++) {
double max_h = -1e9; int id = 1;
for (rint j = 1; j <= k; j++) {
double _h = h(a[i], b[j]);
if (_h > max_h) {
max_h = _h;
id = j;
}
}
b[id] = merge(b[id], a[i]);
if (area(b[id]) <= 0) {
ok = 0;
break;
}
}
if (ok) break;
}
for (rint i = 1; i <= k; i++) {
printf("%lld %lld\n", b[i].x1, b[i].y1);
}
return 0;
}
谈谈正经的做法。
// Author: wlzhouzhuan
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
#define all(x) x.begin(), x.end()
inline int read() {
int x = 0, neg = 1; char op = getchar();
while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
return neg * x;
}
inline void print(int x) {
if (x < 0) { putchar('-'); x = -x; }
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
inline void ckmin(int &a, int b) {
if (a > b) a = b;
}
inline void ckmax(int &a, int b) {
if (a < b) a = b;
}
const int N = 1500005;
const int inf = 1e9;
struct rect {
int x1, y1, x2, y2;
} a[N];
int n, K;
pair <int, int> ans[5];
int cnt[N];
void modify(int x, int y, int v) {
for (rint i = 1; i <= n; i++) {
if (a[i].x1 <= x && x <= a[i].x2 && a[i].y1 <= y && y <= a[i].y2) {
cnt[i] += v;
}
}
}
void dfs(int d) {
int x1 = -inf, x2 = inf, y1 = -inf, y2 = inf;
for (rint i = 1; i <= n; i++) if (!cnt[i]) {
ckmax(x1, a[i].x1), ckmax(y1, a[i].y1);
ckmin(x2, a[i].x2), ckmin(y2, a[i].y2);
}
if (d == 1) {
if (x1 <= x2 && y1 <= y2) { // 最后一个点在剩余矩形的交内
ans[1] = make_pair(x1, y1);
for (rint i = 1; i <= K; i++) {
printf("%d %d\n", ans[i].first, ans[i].second);
}
exit(0);
}
return ;
}
for (rint cx = 0; cx < 2; cx++) {
for (rint cy = 0; cy < 2; cy++) {
int px = (cx ? x1 : x2);
int py = (cy ? y1 : y2);
modify(px, py, 1);
ans[d] = make_pair(px, py);
dfs(d - 1);
modify(px, py, -1);
}
}
}
vector <int> h[N];
vector <tuple <int, int, int>> sl[4], sr[4];
int pre[4][N], suf[4][N], id[N][4];
int fir(tuple <int, int, int> a) {
return get<0>(a);
}
int sec(tuple <int, int, int> b) {
return get<1>(b);
}
int thi(tuple <int, int, int> c) {
return get<2>(c);
}
vector <int> G[N];
void add(int u, int v) { G[u].push_back(v); }
int dfn[N], low[N], dtot;
int ins[N], be[N], scc;
stack <int> st;
void tarjan(int u) {
dfn[u] = low[u] = ++dtot;
st.push(u), ins[u] = 1;
for (auto v: G[u]) {
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (ins[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
int y; ++scc;
while (y = st.top()) {
ins[y] = 0, st.pop();
be[y] = scc;
if (y == u) break;
}
}
}
void solve() {
int x1 = -inf, x2 = inf, y1 = -inf, y2 = inf;
for (rint i = 1; i <= n; i++) {
ckmax(x1, a[i].x1), ckmax(y1, a[i].y1);
ckmin(x2, a[i].x2), ckmin(y2, a[i].y2);
}
//cerr << "ok1 !\n";
//assert(x1 >= x2 && y1 >= y2);
int tot = 0, min_h = inf;
for (rint i = 1; i <= n; i++) {
vector <int> o = h[i];
// 两条纵边 (0 <= id <= 1)
if (a[i].x1 <= x1 && x1 <= a[i].x2) o.pb(0);
if (a[i].x1 <= x2 && x2 <= a[i].x2) o.pb(1);
// 两条横边 (2 <= id <= 3)
if (a[i].y1 <= y1 && y1 <= a[i].y2) o.pb(2);
if (a[i].y1 <= y2 && y2 <= a[i].y2) o.pb(3);
ckmin(min_h, o.size());
if (o.size() >= 3) { // 一定与一条边有交
//o.clear();
continue;
}
id[i][0] = ++tot, id[i][1] = ++tot;
if (o.size() == 1) add(id[i][1], id[i][0]);
int siz = o.size();
for (rint j = 0; j < siz; j++) {
sl[o[j]].emplace_back(o[j] < 2 ? max(y2, a[i].y1) : max(x2, a[i].x1), id[i][j], id[i][j ^ 1]);
sr[o[j]].emplace_back(o[j] < 2 ? min(y1, a[i].y2) : min(x1, a[i].x2), id[i][j], id[i][j ^ 1]);
}
//o.pb(0);
h[i] = o;
//printf("%d\n", h[i].size());
}
//exit(0);
//cerr << "min_h" << ' ' << min_h << '\n';
//assert(min_h > 0);
//cerr << "ok2 !\n";
for (rint v = 0; v < 4; v++) {
sort(all(sl[v])), sort(all(sr[v]));
int sz_l = sl[v].size(), sz_r = sr[v].size();
for (rint i = 0; i < sz_r; i++) {
pre[v][i + 1] = ++tot;
if (i > 0) add(pre[v][i + 1], pre[v][i]);
add(pre[v][i + 1], thi(sr[v][i]));
}
for (rint i = 0, j = 0; i < sz_l; i++) {
while (j < sz_r && fir(sr[v][j]) < fir(sl[v][i])) j++;
if (j > 0) add(sec(sl[v][i]), pre[v][j]);
}
for (rint i = sz_l - 1; i >= 0; i--) {
suf[v][i] = ++tot;
if (i + 1 < sz_l) add(suf[v][i], suf[v][i + 1]);
add(suf[v][i], thi(sl[v][i]));
}
for (rint i = sz_r - 1, j = sz_l; i >= 0; i--) {
while (j > 0 && fir(sl[v][j - 1]) > fir(sr[v][i])) j--;
if (j < sz_l) add(sec(sr[v][i]), suf[v][j]);
}
}
//printf("tot=%d\n", tot);
//for (rint i = 1; i <= tot; i++) {
// printf("%d\n", G[i].size());
//}
//exit(0);
//cerr << "ok3 !\n";
for (rint i = 1; i <= tot; i++) if (!dfn[i]) {
tarjan(i);
}
//cerr << "ok4 !\n";
vector <int> L(5), R(4);
L[0] = L[1] = y2, L[2] = L[3] = x2;
R[0] = R[1] = y1, R[2] = R[3] = x1;
//cerr << "ok5 !\n";
for (rint i = 1; i <= n; i++) {
if (!id[i][0]) continue;
assert(h[i].size());
int j = h[i][be[id[i][0]] >= be[id[i][1]]]; // 拓扑序反过来
//printf("%d: %d\n", i, be[id[i][0]] >= be[id[i][1]]);
ckmax(L[j], j < 2 ? a[i].y1 : a[i].x1);
ckmin(R[j], j < 2 ? a[i].y2 : a[i].x2);
}
//cerr << "ok6! \n";
assert(L[0] <= R[0] && L[1] <= R[1] && L[2] <= R[2] && L[3] <= R[3]);
printf("%d %d\n", x2, L[1]);
printf("%d %d\n", x1, L[0]);
printf("%d %d\n", L[3], y2);
printf("%d %d\n", L[2], y1);
}
int main() {
//freopen("04-14.txt.in", "r", stdin);
//freopen("my.out", "w", stdout);
scanf("%d%d", &n, &K);
for (rint i = 1; i <= n; i++) {
scanf("%d%d%d%d", &a[i].x1, &a[i].y1, &a[i].x2, &a[i].y2);
}
dfs(K);
assert(K == 4);
solve();
return 0;
}
Day1T3 扫除
题目链接:Day1T3 扫除
Solution
Code
咕咕咕
Day2T1 变色龙之恋
题目链接:Day2T1 变色龙之恋
Day2T2 有趣的Joitter交友
题目链接:Day2T2 有趣的Joitter交友
Day2T3 遗迹
题目链接:Day2T3 遗迹
Day3T1 星座3
题目链接:Day3T1 星座3
Day3T2 收获
题目链接:Day3T2 收获
Day3T3 迷路的猫
题目链接:Day3T3 迷路的猫
Day4T1 首都
题目链接:Day4T1 首都
Day4T2 制作团子
题目链接:Day4T2 制作团子
Day4T3 应对方案
题目链接:Day4T3 应对方案