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

待补

posted @ 2025-05-23 23:05  maburb  阅读(39)  评论(0)    收藏  举报