第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(昆明)
BEF 没补, 出题人定义的是hard, 是给金牌S+队伍写的, 菜鸡根本写不到, 写到了考场也出不了, 就放弃了
A - AC
反悔贪心模型, 用堆来维护
将 ((i, i + 1)) 变成 (ac), 下次后悔这部操作, 而改成 ((i - 1, i) (i + 1, i + 2))
至于怎么保存修改的位置? 对每个操作的位置维护一个区间, 表示这步是将 ([l, r]) 改成 (ac)
当这步操作从对选出来的时候, 直接暴力打个标记表示 ([l, r]) 要改
注意到上次选出来这个操作是 ([l', r']) 且 (l < l', r' < r) 我们需要打标记的位置是基于上一次的, 最终打标记的复杂度还是 (O(n))
输出字符串的时候, 对于每个打标记的去点左端点开始 变成 'a' 知道这个区间结束, 这个区间的长度一定是偶数
#include <bits/stdc++.h>
#define fi first
#define se second
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
using PII = pair<int, int>;
const int N = 5e5 + 5;
int n, k, m, pre[N], nxt[N], l[N], r[N], c[N];
char s[N], t[] = "ac";
bool v[N], g[N];
void del(int x) { nxt[pre[x]] = nxt[x]; pre[nxt[x]] = pre[x]; }
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> k >> s + 1; priority_queue<PII> q;
rep(i, 1, n - 1) {
c[i] = (s[i] != 'a') + (s[i + 1] != 'c'); q.push({ -c[i], i });
pre[i] = i - 1, nxt[i] = i + 1; l[i] = i, r[i] = i + 1;
} pre[n] = n - 1; r[0] = 1;
while (m < n / 2) {
int x = q.top().se; q.pop();
if (v[x]) continue; k -= c[x];
if (k < 0) break; ++m;
rep(i, l[x], r[x]) if (!g[i]) g[i] = 1; else break;
per(i, r[x], l[x]) if (!g[i]) g[i] = 1; else break;
v[pre[x]] = v[nxt[x]] = 1;
if (pre[x] && nxt[x] != n) {
l[x] = l[pre[x]], r[x] = r[nxt[x]];
c[x] = c[pre[x]] + c[nxt[x]] - c[x];
del(pre[x]), del(nxt[x]); q.push({ -c[x], x });
}
else if (pre[x] == 0 && nxt[x] != n) del(x), del(nxt[x]);
else if (pre[x] && nxt[x] == n) del(pre[x]), del(x);
}
cout << m << '
';
rep(i, 1, n) if (g[i]) s[i] = 'a', s[++i] = 'c'; cout << s + 1;
return 0;
}
C - Cities
很模板的区间dp, 本来是要(O(n^3)) 的, 但由于每个领主最多只有15坐城,
寻找区间分割点的时候可以直接枚举和 区间右端点属于相同领主的点即可, 则可优化到 (O(15 * n^2))
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
template<class T1, class T2> bool umin(T1& a, T2 b) { return a > b ? (a = b, true) : false; }
const int N = 5e3 + 5;
int n, m, _;
int a[N], f[N][N], pre[N], ls[N];
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
for (cin >> _; _; --_) {
cin >> n; rep (i, 1, n) ls[i] = 0;
rep (i, 1, n) {
cin >> a[i];
if (a[i] == a[i - 1]) --i, --n;
else pre[i] = ls[a[i]], ls[a[i]] = i;
}
per (i, n, 1) rep (j, i + 1, n) {
f[i][j] = f[i][j - 1] + 1;
if (a[i] == a[j]) umin(f[i][j], f[i + 1][j - 1] + 1);
for (int k = pre[j]; k > i; k = pre[k]) umin(f[i][j], f[i][k - 1] + f[k][j] + (a[i] != a[j]));
}
cout << f[1][n] << '
';
}
return 0;
}
D - Competition Against a Robot
结论题, 靠猜, 想要证明去知乎吧, 太麻烦了, 等你证出来比赛结束了, 数论大佬当我没说
一共有(k^n)个序列, 对于序列你可以让这个序列, 变成其他(n)个中的一个, 最好的划分是,
让当前可变成的序列分别代表一(p)的值, 正好对应(p) 属于 [0, n), 大胆的猜
当 (n | k^n) 有解, 每个序列代表一个数字, 代表数字 x 的有序列集合大小相等
至于怎么求是否整除, gcd就行
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a)i<=(b);++i)
using namespace std;
using ll = long long;
ll n, m, _, k;
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
for (cin >> _; _; --_) {
cin >> n >> k;
for (k = __gcd(k, n); n - 1 && k - 1; k = __gcd(n /= k, k));
cout << (n - 1 ? "ROBOT
" : "HUMAN
");
}
return 0;
}
G - Gift
又是dp模型
先对朋友按生日升序排序
(f(i, j, k)) 表示第(i)个朋友花了(j)天做蛋糕送了(k)个礼物的最大满意度
转移方程就很好写了,(中间的合法在代码里看吧)
啥都不干(f(i, j, k) = f(i - 1, j, k))
给(i)做蛋糕(f(i, j, k) = f(i - 1, j - c_i, k) + v_i)
给(i)送礼物(f(i, j, k) = f(i - 1, j - c_i, k - 1) - mx_{k - 1} + mx_k)
其中(mx_i) 表示只送(i)个礼物能获得的最大满意度
千万别忘了2021, 没有2.29
#include <bits/stdc++.h>
#define fi first
#define se second
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
using PII = pair<int, int>;
using ll = long long;
const int N = 505;
struct node { int y, m, d, c, v; } a[N];
int n, m, _, w;
int day[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
ll f[370][20], d[20];
PII b[20];
int main() {
for (scanf("%d", &_); _; --_) {
scanf("%d%d%d", &n, &m, &w); ll mx = 0;
memset(f, 0xcf, sizeof f); f[0][0] = 0; memset(d, 0xcf, sizeof d);
rep(i, 1, n) {
scanf("%d-%d-%d %d %d", &a[i].y, &a[i].m, &a[i].d, &a[i].c, &a[i].v); a[i].y = a[i].d;
if (a[i].m == 2 && a[i].d == 29) { --i, --n; continue; }
rep(j, 1, a[i].m - 1) a[i].y += day[j];
}
rep(i, 0, m - 1) scanf("%d%d", &b[i].fi, &b[i].se);
sort(a + 1, a + 1 + n, [](node& a, node& b) { return a.y < b.y; });
rep(i, 0, (1 << m) - 1) {
ll c = 0, v = 0, g = 0;
rep(j, 0, m - 1) if (i >> j & 1) {
c += b[j].fi; ++g; v += b[j].se;
if (c > w) break;
}
if (c <= w) d[g] = max(d[g], v);
}
while (d[m] < 0) --m;
rep(i, 1, n) per(j, a[i].y, 0) per(k, m, 0) {
if (j >= a[i].c) f[j][k] = max(f[j][k], f[j - a[i].c][k] + a[i].v);
if (k) f[j][k] = max(f[j][k], f[j][k - 1] - d[k - 1] + d[k]);
mx = max(mx, f[j][k]);
}
cout << mx << '
';
}
return 0;
}
H - Hard Calculation
温暖人心
#include <bits/stdc++.h>
using namespace std;
int main() {
int n; cin >> n; cout << 2020 + n;
return 0;
}
I - Mr. Main and Windmills
求每个风车和其他风车的连线与线段(ST)的交点距离(S)的距离排序就行
#include <bits/stdc++.h>
using namespace std;
struct Point {
double x, y; Point(double X = 0, double Y = 0) { x = X, y = Y; }
inline void in() { cin >> x >> y; }
inline void out() { cout << setiosflags(ios::fixed) << setprecision(10) << x << ' ' << y << '
'; }
};
inline double Cro(Point a, Point b) { return a.x * b.y - a.y * b.x; }
inline Point operator-(Point a, Point b) { return Point(a.x - b.x, a.y - b.y); }
inline Point operator+(Point a, Point b) { return Point(a.x + b.x, a.y + b.y); }
inline Point operator*(Point a, double b) { return Point(a.x * b, a.y * b); }
inline Point cross_LL(Point a, Point b, Point c, Point d, double& len) {
Point x = b - a, y = d - c, z = a - c; len = Cro(y, z) / Cro(x, y);
return a + x * len;
}
int n, m;
Point s, e, a[1005];
vector<pair<double, Point>> v[1005];
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> m; s.in(); e.in();
for (int i = 1; i <= n; ++i) {
a[i].in();
for (int j = 1; j < i; ++j) {
double len; Point x = cross_LL(s, e, a[i], a[j], len);
if (len > 1 || len < 0) continue;
v[i].emplace_back(len, x); v[j].emplace_back(len, x);
}
}
for (int i = 1; i <= n; ++i) sort(v[i].begin(), v[i].end(), [](pair<double, Point>& a, pair<double, Point>& b) { return a.first < b.first; });
for (int i = 1; i <= m; ++i) {
int k, h; cin >> k >> h;
if (v[k].size() < h) { cout << "-1
"; continue; }
v[k][h - 1].second.out();
}
return 0;
}
J - Parallel Sort
主要处理置换环的问题, 而我们可以很轻松的用一次, 把环拆成两个两个的环,
递归处理即可
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, a[N], b[N];
void dfs(int x, int y, vector<int>& c) {
if (a[x] == y) return; int t = b[y];
c.emplace_back(x), c.emplace_back(b[y]);
a[t] = a[x]; b[a[x]] = t; a[x] = y; b[y] = x;
if (t != a[t]) dfs(a[t], t, c);
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i], b[a[i]] = i;
vector<vector<int>> ans(1);
for (int i = 1; i <= n; ++i) if (a[i] != i) dfs(a[i], i, ans.back());
if (!ans.back().empty()) ans.emplace_back(vector<int>());
for (int i = 1; i <= n; ++i) if (a[i] != i) {
ans.back().emplace_back(a[i]), ans.back().emplace_back(i);
a[b[i]] = a[i]; b[a[i]] = a[i]; a[i] = i; b[i] = i;
}
if (ans.back().empty()) ans.pop_back();
cout << ans.size() << '
';
for (auto& i : ans) {
cout << (i.size() >> 1) << ' ';
for (auto& j : i) cout << j << ' '; cout << "
";
}
return 0;
}
K - Riichi!!
离谱, 比几何过的还少, 不就到模拟吗
就几个函数
- 判断当前状态是否赢(枚举哪张牌时对子, 其他的牌从小到大枚举要么是对子要么是顺子, (O(14 * 14)))
- 枚举当前仍哪张牌, 在从小到大枚举起到哪张牌是获胜(调用1, O(14 * 34))
又不复杂
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define fi first
#define se second
using namespace std;
int n, m, _, cnt[4][10], a[4][10];
char s[30];
map<char, int> st;
map<int, char> ts;
void init() {
memset(cnt, 0, sizeof cnt);
for (int i = 2; i <= 28; i += 2) ++cnt[st[s[i]]][s[i - 1] ^ '0'];
}
bool check(int cnt[][10]) {
memcpy(a, cnt, sizeof a);
rep(i, 0, 3) rep(j, 1, 9) if (a[i][j]) {
if (a[i][j] > 2) a[i][j] -= 3;
if (i == 3 && a[i][j]) return 0;
if (!a[i][j]) continue;
if (j > 7 || min(a[i][j + 1], a[i][j + 2]) < a[i][j]) return 0;
a[i][j + 1] -= a[i][j], a[i][j + 2] -= a[i][j]; a[i][j] = 0;
}
return 1;
}
bool win(int c[][10]) {
rep(i, 0, 3) rep(j, 1, 9) if (c[i][j] >= 2) {
c[i][j] -= 2;
bool f = check(c); c[i][j] += 2;
if (f) return 1;
}
return 0;
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
ts[st['w'] = 0] = 'w'; ts[st['b'] = 1] = 'b'; ts[st['s'] = 2] = 's'; ts[st['z'] = 3] = 'z';
for (cin >> _; _; --_) {
cin >> s + 1; init();
if (win(cnt)) { cout << "Tsumo!
"; continue; }
vector<pair<pair<int, char>, vector<pair<int, char>>>> ans;
rep(i, 0, 3) rep(j, 1, 9) if (cnt[i][j]) {
--cnt[i][j]; ans.emplace_back(make_pair(j, ts[i]), vector<pair<int, char>>());
rep(x, 0, 3) rep(y, 1, 9) if (x != i || y != j) {
++cnt[x][y];
if (win(cnt)) ans.back().se.emplace_back(y, ts[x]);
--cnt[x][y];
}
++cnt[i][j];
if (ans.back().se.empty()) ans.pop_back();
}
cout << ans.size() << "
";
for (auto &cur : ans) {
cout << cur.fi.fi << cur.fi.se << ' ';
for (auto &j : cur.se) cout << j.fi << j.se; cout << '
';
}
}
return 0;
}
L - Simone and graph coloring
逆序对而已, 自己前面比自己大的用了 k 个颜色, 拿自己就用颜色 k + 1
#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 5;
int n, m, _, c[N], a[N], col[N];
void add(int x, int k) { for (; x; x -= -x & x) c[x] = max(c[x], k); }
int ask(int x) { int ans = 0; for (; x <= n; x += -x & x) ans = max(ans, c[x]); return ans; }
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
for (cin >> _ ; _; --_) {
cin >> n; m = 0;
for (int i = 1; i <= n; ++i) c[i] = 0, cin >> a[i];
for (int i = 1; i <= n; ++i) {
col[i] = ask(a[i]) + 1; m = max(m, col[i]);
add(a[i], col[i]);
}
cout << m << '
';
for (int i = 1; i <= n; ++i) cout << col[i] << ' '; cout << '
';
}
return 0;
}
M - Stone Games
主席树板子题
从小到达枚举加不到的数, 即
值域在([1, k - 1])的数和小于 (k - 1), 然后(k += sum[1, k - 1])
计算一下最多枚举多少次k
int main() {
IOS;
for (ll ls = 0, c = 1, s = 0; c <= 1e9; ++n, s += ls + 1, ls = c, c = s + 1);
cout << n;
return 0;
}
42 次
不过还不放心, 打下表, 你会发现时 斐波拉契F(i) - 1
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
using namespace std;
using ll = long long;
const int N = 1e6 + 5;
struct BIT {
struct node { int l, r; ll val; } tr[N * 32];
int rt[N], tot;
void update(int& x, int y, int l, int r, int d) {
tr[x = ++tot] = tr[y]; tr[x].val += d;
if (l == r) return;
int mid = l + r >> 1;
if (mid >= d) update(tr[x].l, tr[y].l, l, mid, d);
else update(tr[x].r, tr[y].r, mid + 1, r, d);
}
ll ask(int x, int y, int l, int r, int d) {
if (l == r) return tr[x].val - tr[y].val;
int mid = l + r >> 1;
if (mid >= d) return ask(tr[x].l, tr[y].l, l, mid, d);
return tr[tr[x].l].val - tr[tr[y].l].val + ask(tr[x].r, tr[y].r, mid + 1, r, d);
}
} bit;
int n, m, k;
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> m;
rep (i, 1, n) cin >> k, bit.update(bit.rt[i], bit.rt[i - 1], 1, 1e9, k);
ll ls = 0;
rep (_, 1, m) {
ll l, r, ans = 2; cin >> l >> r; l = (l + ls) % n + 1, r = (r + ls) % n + 1;
if (l > r) swap(r, l);
while (1) {
ll s = bit.ask(bit.rt[r], bit.rt[l - 1], 1, 1e9, min(ans - 1, (ll)1e9));
if (s >= ans - 1) ans = s + 2;
else break;
}
cout << (ls = ans - 1) << '
';
}
return 0;
}