The 2018 ACM-ICPC China JiangSu Provincial Programming Contest (held by ChinaUniversity of Mining and Technology)
A. Plague Inc
多源\(bfs\)模板。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, m;
while (std::cin >> n >> m) {
int k;
std::cin >> k;
std::vector<std::vector<int>> d(n, std::vector<int>(m, -1));
std::queue<std::pair<int, int>> q;
for (int i = 0; i < k; ++ i) {
int x, y;
std::cin >> x >> y;
-- x, -- y;
q.emplace(x, y);
d[x][y] = 0;
}
const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
while (q.size()) {
auto [x, y] = q.front(); q.pop();
for (int i = 0; i < 4; ++ i) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 0 || nx >= n || ny < 0 || ny >= m || d[nx][ny] != -1) {
continue;
}
d[nx][ny] = d[x][y] + 1;
q.emplace(nx, ny);
}
}
int ansx = -1, ansy = -1, max = -1;
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < m; ++ j) {
if (d[i][j] > max) {
ansx = i, ansy = j;
max = d[i][j];
}
}
}
std::cout << ansx + 1 << " " << ansy + 1 << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
//std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
B. Array
题意:求长度为\(n\)有\(k\)个逆序对的排列有多少个。
考虑\(dp\)。\(f[i][j]\)表示长度为\([1, i]\)的排列逆序对有\(k\)个的个数,考虑\(i\)怎么放,发现\(i\)放在几个数前面就多几个逆序对。那么\(f[i][j] = \sum_{k=0}^{i-1} f[i - 1][j - k]\)。前缀和优化到\(O(n^2)\)。不能开二维数组,所以需要滚动数组优化,然后需要把询问离线。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int mod = 1e9 + 7;
const int N = 5010;
int f[N], sum[N];
void solve() {
std::vector<std::array<int, 3>> q;
int n, k, id = 0;
while (std::cin >> n >> k) {
q.push_back(std::array<int, 3>{n, k, id ++ });
}
int m = q.size();
std::vector<int> ans(m);
std::sort(q.begin(), q.end());
f[0] = 1;
for (int i = 1, t = 0; i <= 5000; ++ i) {
while (t < m && q[t][0] == i && q[t][1] == 0) {
ans[q[t][2]] = 1;
++ t;
}
for (int j = 1; j <= 5000; ++ j) {
f[j] = j < i ? sum[j] : (sum[j] - sum[j - i] + mod) % mod;
while (t < m && q[t][0] == i && q[t][1] == j) {
ans[q[t][2]] = f[j];
++ t;
}
}
sum[0] = 1;
for (int j = 1; j <= 5000; ++ j) {
sum[j] = (sum[j - 1] + f[j]) % mod;
}
}
for (int i = 0; i < m; ++ i) {
std::cout << ans[i] << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
//std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
D. Persona5
多重排列模板题。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 1000010, mod = 1e9 + 7;
int fact[N], infact[N];
int a[N];
int power(int a, int b) {
int res = 1;
for (;b;b >>= 1, a = 1ll * a * a % mod) {
if (b & 1) {
res = 1ll * res * a % mod;
}
}
return res;
}
void solve() {
fact[0] = infact[0] = 1;
for (int i = 1; i < N; ++ i) {
fact[i] = (i64)i * fact[i - 1] % mod;
}
infact[N - 1] = power(fact[N - 1], mod - 2);
for (int i = N - 1; i > 1; -- i) {
infact[i - 1] = (i64)i * infact[i] % mod;
}
int n;
while (std::cin >> n) {
int sum = 0;
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
sum += a[i];
}
int ans = fact[sum];
for (int i = 0; i < n; ++ i) {
ans = (i64)ans * infact[a[i]] % mod;
}
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;
while (t -- ) {
solve();
}
return 0;
}
E. Massage
题意:从\((1, 1)\)走到\((n, m)\)走两次,只能向下或向右走,每个格子只能走一次。求方案数。
一点点容斥。
实际可以看作两个起点:\((1, 2), (2, 1)\)。路径显然是\((1, 2)\)->\((n - 1, m), (2, 1)\)-> \((n, m - 1)\)。这两个都可以用组合数算。但还是会有重复走的格子,发现如果重复,一定是从\((1, 2)\)走到\((n, m - 1)\)和从\((2, 1)\)走到\((n - 1, m)\)。减去这个方案数就行。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 1010, mod = 1e9 + 7;
int fact[N], infact[N];
int power(int a, int b) {
int res = 1;
for (;b;b >>= 1, a = 1ll * a * a % mod) {
if (b & 1) {
res = 1ll * res * a % mod;
}
}
return res;
}
void solve() {
fact[0] = infact[0] = 1;
for (int i = 1; i < N; ++ i) {
fact[i] = (i64)i * fact[i - 1] % mod;
}
infact[N - 1] = power(fact[N - 1], mod - 2);
for (int i = N - 1; i > 1; -- i) {
infact[i - 1] = (i64)i * infact[i] % mod;
}
auto C = [&](int n, int m) -> int {
if (n < m || m < 0) {
return 0;
}
return (i64)fact[n] * infact[m] % mod * infact[n - m] % mod;
};
int n, m;
while (std::cin >> n >> m) {
int ans = (i64)C(n + m - 4, n - 2) * C(n + m - 4, n - 2) % mod;
ans = (ans - (i64)C(n + m - 4, n - 1) * C(n + m - 4, n - 3) % mod + mod) % mod;
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;
while (t -- ) {
solve();
}
return 0;
}
F. Company
题意:给你一个森林,每个点有点权\(a_i\)。求有多少三元组\((i, j, k)\)。满足\(i\)是\(j\)的祖先,\(j\)是\(k\)祖先,且\(a_j > a_i, a_j > a_k\)。
对于每个点,求出祖先节点有多少个小于它的,子树里有多少个小于它的,然后乘起来就是这个点作为\(j\)贡献的答案。
求祖先节点小于它的数直接\(dfs\)加树状数组就行。求子树有多少小于它的,可以用\(dsu\ on\ tree\),也就是树上启发式合并。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
struct Fenwick {
std::vector<int> tr;
int n;
Fenwick(){};
Fenwick(int _n) : n(_n) {
init(n);
}
void init(int n) {
tr.assign(n + 1, 0);
}
void clear() {
for (int i = 0; i <= n; ++ i) {
tr[i] = 0;
}
}
void add(int x, int v) {
for (int i = x; i <= n; i += i & -i) {
tr[i] += v;
}
}
int query(int x) {
int res = 0;
for (int i = x; i ; i -= i & -i) {
res += tr[i];
}
return res;
}
int sum(int l, int r) {
return query(r) - query(l - 1);
}
}tr(1000000);
void solve() {
int n;
while (std::cin >> n) {
std::vector<std::vector<int>> adj(n);
std::vector<int> fa(n);
for (int i = 0; i < n; ++ i) {
std::cin >> fa[i];
-- fa[i];
if (fa[i] >= 0) {
adj[fa[i]].push_back(i);
}
}
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<int> size(n), son(n, -1);
auto dfs = [&](auto & self, int u) -> void {
size[u] = 1;
for (auto & v : adj[u]) {
self(self, v);
size[u] += size[v];
if (son[u] == -1 || size[v] > size[son[u]]) {
son[u] = v;
}
}
};
int Son = -1;
auto update = [&](auto & self, int u, int add) -> void {
if (u == Son) {
return;
}
tr.add(a[u], add);
for (auto & v : adj[u]) {
self(self, v, add);
}
};
i64 ans = 0;
auto dsu = [&](auto & self, int u, bool del) -> void {
int cnt = tr.query(a[u] - 1);
tr.add(a[u], 1);
for (auto & v : adj[u]) {
if (v == son[u]) {
continue;
}
self(self, v, true);
}
if (son[u] != -1) {
self(self, son[u], false);
Son = son[u];
} else {
Son = -1;
}
tr.add(a[u], -1);
update(update, u, 1);
ans += (i64)cnt * (tr.query(a[u] - 1) - cnt);
if (del) {
Son = -1;
update(update, u, -1);
}
};
for (int i = 0; i < n; ++ i) {
if (fa[i] == -1) {
tr.clear();
dfs(dfs, i);
dsu(dsu, i, true);
}
}
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;
while (t -- ) {
solve();
}
return 0;
}
G. Window
题意:求数组\(a\)的所有长度为\(m\)的子数组的元素乘积对\(p\)取模后的和。和不用取模,\(p\)不一定是质数。
看起来是数学题。其实不用管\(p\)是不是质数,直接用线段树维护即可。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
struct SegmentTree {
struct Node {
int l, r;
int sum;
};
std::vector<Node> tr;
int P;
SegmentTree(){};
SegmentTree(std::vector<int> & a, int p) {
int n = a.size();
P = p;
tr.assign(n << 2, {});
auto build = [&](auto & self, int l, int r, int u = 1) -> void {
tr[u] = {l, r};
if (l == r) {
tr[u].sum = a[l];
return;
}
int mid = l + r >> 1;
self(self, l, mid, u << 1);
self(self, mid + 1, r, u << 1 | 1);
tr[u].sum = (i64)tr[u << 1].sum * tr[u << 1 | 1].sum % P;
};
build(build, 0, n - 1);
}
int query(int l, int r, int u = 1) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].sum;
}
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 (i64)query(l, r, u << 1) * query(l, r, u << 1 | 1) % P;
}
};
void solve() {
int n, m, p;
while (std::cin >> n >> m >> p) {
std::vector<int> a(n);
int x, y, z;
std::cin >> a[0] >> x >> y >> z;
a[0] %= p;
for (int i = 1; i < n; ++ i) {
a[i] = ((i64)x * a[i - 1] % p * a[i - 1] % p + (i64)y * a[i - 1] % p + z) % p;
}
if (p == 1) {
std::cout << 0 << "\n";
continue;
}
SegmentTree tr(a, p);
i64 ans = 0;
for (int i = 0; i + m - 1 < n; ++ i) {
ans += tr.query(i, i + m - 1);
}
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;
while (t -- ) {
solve();
}
return 0;
}
J. set
题意:\([1, n]\)的每个子集的价值为每个数的乘积的平方,合法子集是指没有两个相邻的数的子集。求所有合法子集的和。
记\(f[i][0/1]\)表示前\(i\)个数组成的集合放不放\(i\)的价值,那么\(f[i][1] = i^2 \times f[i - 1][0], f[i][0] = f[i - 1][0] + f[i - 1][1]\)。需要使用高精度。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
struct Int {
std::vector<int> a;
Int() {
a.assign(1, 0);
}
Int(int n) {
do {
a.push_back(n % 10);
n /= 10;
} while (n);
}
int & operator[](int i) {
return a[i];
}
int size() {
return (int)a.size();
}
};
Int operator + (Int & a, Int & b) {
Int res;
int n = a.size(), m = b.size();
res.a.assign(std::max(n, m) + 1, 0);
for (int i = 0, t = 0; i < n || i < m || t; ++ i) {
if (i < n) {
t += a[i];
}
if (i < m) {
t += b[i];
}
res[i] = t % 10;
t /= 10;
}
while (res.size() > 1 && res.a.back() == 0) {
res.a.pop_back();
}
return res;
}
Int operator * (Int & a, int b) {
Int res;
int n = a.size();
res.a.assign(n + 10, 0);
for (int i = 0, t = 0; i < n || t; ++ i) {
if (i < n) {
t += a[i] * b;
}
res[i] = t % 10;
t /= 10;
}
while (res.size() > 1 && res.a.back() == 0) {
res.a.pop_back();
}
return res;
}
Int sub(Int a) {
int n = a.size();
for (int i = 0; i < n; ++ i) {
if (a[i] == 0) {
a[i] = 9;
} else {
-- a[i];
break;
}
}
while (a.size() > 1 && a.a.back() == 0) {
a.a.pop_back();
}
return a;
}
std::ostream & operator << (std::ostream & out, const Int & a) {
for (int i = (int)a.a.size() - 1; i >= 0; -- i) {
out << a.a[i];
}
return out;
}
const int N = 110;
Int f[N][2];
void solve() {
f[0][0] = 1;
for (int i = 1; i < N; ++ i) {
f[i][1] = f[i - 1][0] * (i * i);
f[i][0] = f[i - 1][0] + f[i - 1][1];
}
int n;
while (std::cin >> n) {
std::cout << sub(f[n][0] + f[n][1]) << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
//std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
I. T-shirt
题意:有\(n\)天和\(m\)件衣服。如果某天穿第\(i\)件衣服,第二天穿第\(j\)件衣服,就会有\(f[i][j]\)的价值。给出\(f\)。求一个价值最大的方案。
我们把两个点看作一个点,第一天无法组成一个点所以价值为零,从第二天开始,\((i, j)\)这个点表示从\(i\)到\(j\)的价值。那么我们需要选一条长度为\(n-1\)的路径,每个点可以重复经过,使得经过的点的点权的和最大。
那么这是矩阵乘法经典问题。记\(dp[t][i][j]\)为第\(t\)天走到\((i, j)\)这个点的最大价值。有\(dp[t + 1][i][j] = \max_{k=1}^{m} dp[t][i][k] + dp[t][k][j]\)。于是做矩阵乘法就行。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 100;
using Mat = std::array<std::array<i64, N>, N>;
void clear(Mat & m) {
for (int i = 0; i < N; ++ i) {
for (int j = 0; j < N; ++ j) {
m[i][j] = 0;
}
}
}
Mat operator * (const Mat & a, const Mat & b) {
static Mat res;
clear(res);
for (int k = 0; k < N; ++ k) {
for (int i = 0; i < N; ++ i) {
for (int j = 0; j < N; ++ j) {
res[i][j] = std::max(res[i][j], a[i][k] + b[k][j]);
}
}
}
return res;
}
void solve() {
int n, m;
Mat a, res;
while (std::cin >> n >> m) {
clear(a);
for (int i = 0; i < m; ++ i) {
for (int j = 0; j < m; ++ j) {
std::cin >> a[i][j];
}
}
-- n;
clear(res);
for(;n; n >>= 1, a = a * a) {
if (n & 1) {
res = res * a;
}
}
i64 ans = 0;
for (int i = 0; i < m; ++ i) {
for (int j = 0; j < m; ++ j) {
ans = std::max(ans, res[i][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;
while (t -- ) {
solve();
}
return 0;
}
K. Road
题意:给你一个图,删边删到只剩一棵树。使得每个点到起点的最短距离不变。求方案数。
求出每个点有多少个点到它的距离是最短距离,那么就是选一个保留不删,方案数就是这些点的数量。每个点的方案数乘起来就是答案。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int mod = 1e9 + 7;
void solve() {
int n;
while (std::cin >> n) {
std::vector<std::vector<std::pair<int, int>>> adj(n);
for (int i = 0; i < n; ++ i) {
std::string s;
std::cin >> s;
for (int j = 0; j < n; ++ j) {
if (s[j] != '0') {
adj[i].emplace_back(j, s[j] - '0');
}
}
}
using PII = std::pair<int, int>;
std::priority_queue<PII, std::vector<PII>, std::greater<PII>> heap;
const int inf = 1e9;
std::vector<int> dist(n, inf), cnt(n, 0);
heap.emplace(0, 0);
dist[0] = 0;
cnt[0] = 1;
while (heap.size()) {
auto [d, u] = heap.top(); heap.pop();
if (d != dist[u]) {
continue;
}
for (auto & [v, w] : adj[u]) {
if (dist[v] > d + w) {
dist[v] = d + w;
cnt[v] = 1;
heap.emplace(dist[v], v);
} else if (dist[v] == d + w) {
++ cnt[v];
}
}
}
int ans = 1;
for (int i = 0; i < n; ++ i) {
ans = (i64)ans * cnt[i] % mod;
}
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;
while (t -- ) {
solve();
}
return 0;
}