2018-2019 ACM-ICPC, Asia Shenyang Regional Contest
C. Insertion Sort
题意:一个长度为\(n\)的排列是好的,那么它的最长上升子序列长度至少为\(n-1\)。求有多少长度为\(n\)的排列在给前\(k\)个元素排序后是好的。
考虑\(k=1\)的情况,实际和\(k=0\)一样,因为不会改变任何东西。那么这种情况我们可以任选\(n-1\)个数顺序不变,然后剩下的一个数有\(n\)个位置可以插入,但会有重复,因为相邻两个数会产生两个相同的排列,所以答案是\(n^2 - 2(n-1)\)。
如果\(k \geq n - 1\),答案是\(n!\)。
否则分类讨论一下,如果前面\(k\)个顺便排,那么排序后前面有长度为\(k\)的上升子序列,那么后面需要至少\(n - k - 1\)的上升子序列,这就回到了第一种情况:\(m = n - k\),长度为\(m\)的排列最长上升子序列长度大于等于\(m-1\)的个数,方案数为\(k!\times (n-k)^2 - 2(n-k-1)\)。
否则我们从前\(k\)个数选一个数,插到后面\(n-k\)个数的某个数的后面,方案数为\(k! \times k(n-k)\)。也可以从后面选一个数插到前面,但不能插到\(k\)的前面,这样排序后是\(1, 2, 3 ... i, k, k + 1, ... i - 1, i + 1, ... n\)符合条件,同时不能把\(k+1\)插到前面,不然就和前面的讨论重复了,这样的方案数是\(k! \times (n - k - 1)\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, k, q;
std::cin >> n >> k >> q;
std::vector<int> fact(n + 1);
fact[0] = 1;
for (int i = 1; i <= n; ++ i) {
fact[i] = (i64)fact[i - 1] * i % q;
}
if (k == 1) {
std::cout << (n * n - 2 * (n - 1)) % q << "\n";
return;
}
if (k >= n - 1) {
std::cout << fact[n] << "\n";
return;
}
int ans = 0;
ans = (i64)fact[k] * (n - k - 1) % q;
ans = (ans + (i64)fact[k] * (((n - k) * (n - k) % q - 2 * (n - k - 1) % q + q) % q)) % q;
ans = (ans + (i64)fact[k] * k % q * (n - k) % q) % q;
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
for (int i = 1; i <= t; ++ i) {
std::cout << "Case #" << i << ": ";
solve();
}
return 0;
}
E. The Kouga Ninja Scrolls
题意:\(n\)个人有坐标和编号,三种操作:
- \(1\ k\ x\ y\),给第\(k\)个人的横纵坐标分别加上\(x, y\)。
- \(2\ k\ c\),第\(k\)个人的编号改为\(c\)。
- \(3\ l\ r\$,求\)[l, r]$这个区间两个编号不同的人的最大曼哈顿距离
因为对于一个点,它的每个坐标对答案的贡献要么是正的要么是负的,那么我们可以用\([1, 2^2-1]\)的每个数表示其坐标的正负值,例如\(01\)代表\(-x + y\)。那么如果当前点选择的状态是\(x\),那么另一个的状态就是它的反码:~\(x \&3\)。那么如果不可用编号的限制,我们可以用线段树维护区间内每个状态的最大值。这个做法可以推广到点是\(k\)维。现在考虑编号的限制,我们维护最大值和次大值,并且让最大和次大的编号不同,这样如果两个状态的编号相同,我们就可以拿其中一个最大和另一个次大相加。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
using A = std::array<i64, 4>;
const int N = 1e5 + 5;
const i64 inf = 1e18;
struct Info {
std::array<i64, 4> max1, max2;
std::array<int, 4> max1id, max2id;
Info() {
init();
}
void init() {
max1.fill(-inf);
max2.fill(-inf);
max1id.fill(-1);
max2id.fill(-1);
}
Info(i64 x, i64 y, int c) {
for (int i = 0; i < 4; ++ i) {
i64 v = 0;
if (i & 1) {
v += x;
} else {
v -= x;
}
if (i >> 1 & 1) {
v += y;
} else {
v -= y;
}
max1[i] = v;
max1id[i] = c;
}
max2.fill(-inf);
max2id.fill(-1);
}
};
Info operator + (const Info & a, const Info & b) {
Info res;
for (int i = 0; i < 4; ++ i) {
std::pair<i64, int> v[4] = {
{a.max1[i], a.max1id[i]},
{a.max2[i], a.max2id[i]},
{b.max1[i], b.max1id[i]},
{b.max2[i], b.max2id[i]}
};
for (int j = 0; j < 4; ++ j) {
if (v[j].first > res.max1[i]) {
res.max1[i] = v[j].first;
res.max1id[i] = v[j].second;
}
}
for (int j = 0; j < 4; ++ j) {
if (v[j].second != res.max1id[i] && v[j].first > res.max2[i]) {
res.max2[i] = v[j].first;
res.max2id[i] = v[j].second;
}
}
}
return res;
}
i64 a[N], b[N];
int c[N];
struct SegmentTree {
struct Node {
int l, r;
Info info;
};
std::vector<Node> tr;
SegmentTree(){};
SegmentTree(int n) {
tr.assign(n << 2, {});
build(0, n - 1);
}
void pushup(int u) {
tr[u].info = tr[u << 1].info + tr[u << 1 | 1].info;
}
void build(int l, int r, int u = 1) {
tr[u] = {l, r};
if (l == r) {
tr[u].info = Info(a[l], b[l], c[l]);
return;
}
int mid = l + r >> 1;
build(l, mid, u << 1);
build(mid + 1, r, u << 1 | 1);
pushup(u);
}
void modify(int p) {
int u = 1;
while (tr[u].l != tr[u].r) {
int mid = tr[u].l + tr[u].r >> 1;
if (p <= mid) {
u = u << 1;
} else {
u = u << 1 | 1;
}
}
tr[u].info = Info(a[p], b[p], c[p]);
u >>= 1;
while (u) {
pushup(u);
u >>= 1;
}
}
Info query(int l, int r, int u = 1) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].info;
}
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) {
return query(l, r, u << 1);
} else if (l > mid) {
return query(l, r, u << 1 | 1);
}
return query(l, r, u << 1) + query(l, r, u << 1 | 1);
}
};
void solve() {
int n, m;
std::cin >> n >> m;
for (int i = 0; i < n; ++ i) {
std::cin >> a[i] >> b[i] >> c[i];
}
SegmentTree tr(n);
while (m -- ) {
int op;
std::cin >> op;
if (op == 1) {
int k, x, y;
std::cin >> k >> x >> y;
-- k;
a[k] += x, b[k] += y;
tr.modify(k);
} else if (op == 2) {
int k, z;
std::cin >> k >> z;
-- k;
c[k] = z;
tr.modify(k);
} else {
int l, r;
std::cin >> l >> r;
-- l, -- r;
auto info = tr.query(l, r);
i64 ans = 0;
for (int i = 0; i < 4; ++ i) {
int j = ~i & 3;
if (info.max1id[i] != info.max1id[j]) {
ans = std::max(ans, info.max1[i] + info.max1[j]);
}
if (info.max1id[i] != info.max2id[j]) {
ans = std::max(ans, info.max1[i] + info.max2[j]);
}
if (info.max2id[i] != info.max1id[j]) {
ans = std::max(ans, info.max2[i] + info.max1[j]);
}
if (info.max2id[i] != info.max2id[j]) {
ans = std::max(ans, info.max2[i] + info.max2[j]);
}
}
std::cout << ans << "\n";
}
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
for (int i = 1; i <= t; ++ i) {
std::cout << "Case #" << i << ":\n";
solve();
}
return 0;
}
G. Best ACMer Solves the Hardest Problem
题意:一开始给你\(n\)个点,有\(m\)个操作,操作分为四类:
- \(1\ x\ y\ w\),把\((x, y)\)插入,点权为\(w\)。
- \(2\ x\ y\), 删除\((x, y)\)。
- \(3\ x\ y\ k\ w\),把和\((x, y)\)的距离为\(\sqrt{k}\)的点的点权都加上\(w\)。
- \(4\ x\ y\ k\),求和\((x, y)\)的距离的为\(\sqrt{k}\)的点的点权和。
给了\(12s\),点的范围只有\(6000\),显然暴力。
不过也只能支持两层循环,注意到对于每个\(c\)的\(a^2 + b^2 = c\)的\(a, b\)对很少,那么可以枚举出来,然后对于每个点,和它距离为\(k\)的点就是\((x - a, y - b), (x - a, y + b), (x + a, y - b), (x + a, y + b)\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 1e7 + 1;
std::vector<std::vector<std::pair<int, int>>> R(N);
void init() {
for (int i = 0; i <= 6000; ++ i) {
for (int j = 0; j <= 6000 && i * i + j * j < N; ++ j) {
R[i * i + j * j].emplace_back(i, j);
}
}
}
void solve() {
int n, m;
std::cin >> n >> m;
std::map<std::pair<int, int>, i64> mp;
for (int i = 0; i < n; ++ i) {
int x, y, w;
std::cin >> x >> y >> w;
mp[{x, y}] = w;
}
i64 ans = 0;
while (m -- ) {
int op, x, y;
std::cin >> op >> x >> y;
x = (x + ans) % 6000 + 1, y = (y + ans) % 6000 + 1;
if (op == 1) {
int w;
std::cin >> w;
mp[{x, y}] = w;
} else if (op == 2) {
mp.erase({x, y});
} else if (op == 3) {
int k, w;
std::cin >> k >> w;
for (auto & [a, b] : R[k]) {
for (auto & i : {-1, 1}) {
for (auto & j : {-1, 1}) {
//x - x1 = i * a
int x1 = x - i * a, y1 = y - j * b;
if (x1 < 1 || x1 > 6000 || y1 < 1 || y1 > 6000) {
continue;
}
if (mp.count({x1, y1})) {
mp[{x1, y1}] += w;
}
if (b == 0) {
break;
}
}
if (a == 0) {
break;
}
}
}
} else {
int k;
std::cin >> k;
ans = 0;
for (auto & [a, b] : R[k]) {
for (auto & i : {-1, 1}) {
for (auto & j : {-1, 1}) {
//x - x1 = i * a
int x1 = x - i * a, y1 = y - j * b;
if (x1 < 1 || x1 > 6000 || y1 < 1 || y1 > 6000) {
continue;
}
if (mp.count({x1, y1})) {
ans += mp[{x1, y1}];
}
if (b == 0) {
break;
}
}
if (a == 0) {
break;
}
}
}
std::cout << ans << "\n";
}
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
init();
std::cin >> t;
for (int i = 1; i <= t; ++ i) {
std::cout << "Case #" << i << ":\n";
solve();
}
return 0;
}
J. How Much Memory Your Code Is Using?
签到题。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
int sum = 0;
for (int i = 0; i < n; ++ i) {
std::string s, t;
std::cin >> s >> t;
if (s == "long" && (t == "double" || t == "long")) {
s = s + " " + t;
std::cin >> t;
}
int cnt = 1;
int p = t.find('['), q = t.find(']');
if (p != -1) {
cnt *= std::stoi(t.substr(p + 1, q - p - 1));
}
if (s == "bool" || s == "char") {
} else if (s == "int" || s == "float") {
cnt *= 4;
} else if (s == "double" || s == "long long") {
cnt *= 8;
} else {
cnt *= 16;
}
sum += cnt;
}
std::cout << (sum + 1023) / 1024 << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
for (int i = 1; i <= t; ++ i) {
std::cout << "Case #" << i << ": ";
solve();
}
return 0;
}
K. Let the Flames Begin
题意:约瑟夫环问题变种,求第\(m\)个出队的人,范围很大,但\(m, k\)中有一个小于等于\(2e6\)的。
分类讨论,如果\(m < k\),则是求第\(m\)个人出队的板子。
如果\(k < m\),那么就是一个求第\(m\)个人出队且\(k\)极小\(n\)极大的板子。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
i64 n, m, k;
std::cin >> n >> m >> k;
if (m < k) {
i64 ans = (k - 1) % (n - m + 1);
for (i64 i = n - m + 2; i <= n; ++ i) {
ans = (ans + k) % i;
}
std::cout << ans + 1 << "\n";
} else {
if (k == 1) {
std::cout << m << "\n";
return;
}
i64 ans = (k - 1) % (n - m + 1);
for (i64 i = n - m + 2, j = i; i <= n; i = j + 1) {
i64 t = (i - 1 - ans + k - 2) / (k - 1);
if (i + t - 1 >= n) {
ans = (ans + (n - i + 1) * k) % n;
break;
}
ans = (ans + t * k) % (i + t - 1);
j = i + t - 1;
}
std::cout << ans + 1 << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
for (int i = 1; i <= t; ++ i) {
std::cout << "Case #" << i << ": ";
solve();
}
return 0;
}
L. Machining Disc Rotors
待补

浙公网安备 33010602011771号