ACIM-ICPC 2018 南京赛区网络预赛
A. Olympian Math Problem
题意:求\((\sum_{i=1}^{n-1} i \times i!) \% n\)。
\(i \times i! = (i + 1)! - i!\),\(\sum_{i=1}^{n-1} i \times i! = (2! - 1!) + (3! - 2!) + ... + (n! - (n-1)!) = n! - 1\)。模\(n\)后就是\(n-1\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
i64 n;
std::cin >> n;
std::cout << n - 1 << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie();
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
B. writing on the wall
题意:一个\(01\)矩阵,求不包含\(1\)的子矩阵的数量。
枚举\(i\)行作为矩形的底边,枚举\(j\)列为行的右边,那么矩形的右下角就是\((i, j)\),我们需要求出有多少合法的左上角。
记\(h[i][j]\)为\((i, j)\)向上不遇到\(1\)可以走的最大距离。那么就变了一个柱形图,对于每个\((i, j)\),对于每个\(k < j\)合法的左上角的答案为\(\min(h[i][k], h[i][j])\)。那么我们可以单调栈维护这个柱形图,使得高度单调上升,那么对于新加入的\(h[i][j]\),把前面高度大于等于它的都弹出,那么前面的高度都小于等于它,记录前面的和就行了。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::vector<int>> a(n, std::vector<int>(m));
for (int i = 0; i < k; ++ i) {
int x, y;
std::cin >> x >> y;
-- x, -- y;
a[x][y] = 1;
}
std::vector<int> h(m);
i64 ans = 0;
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < m; ++ j) {
if (a[i][j]) {
h[j] = 0;
} else {
h[j] += 1;
}
}
i64 sum = 0;
std::stack<std::pair<int, int>> stk;
for (int j = 0; j < m; ++ j) {
int cnt = 1;
while (stk.size() && stk.top().first >= h[j]) {
auto [x, y] = stk.top(); stk.pop();
sum -= (i64)x * y;
cnt += y;
}
stk.emplace(h[j], cnt);
sum += (i64)h[j] * cnt;
ans += sum;
}
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie();
int t = 1;
std::cin >> t;
for (int i = 1; i <= t; ++ i) {
std::cout << "Case #" << i << ": ";
solve();
}
return 0;
}
E. Challenge
题意:第\(t\)次选第\(i\)个元素有\(t\times a_i + b_j\)的价值。如果想选第\(i\)个元素,则需要先选给出的\(p_i\)个元素,求最大价值。
\(n\)很小,状压即可。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<i64> a(n), b(n);
std::vector<int> st(n);
for (int i = 0; i < n; ++ i) {
int m;
std::cin >> a[i] >> b[i] >> m;
while (m -- ) {
int j;
std::cin >> j;
-- j;
st[i] |= 1 << j;
}
}
const i64 inf = 1e18;
std::vector<i64> f(1 << n, -inf);
f[0] = 0;
i64 ans = -inf;
for (int i = 0; i < 1 << n; ++ i) {
i64 t = __builtin_popcount(i);
ans = std::max(ans, f[i]);
for (int j = 0; j < n; ++ j) {
if ((i >> j & 1) == 0 && (i & st[j]) == st[j]) {
f[i + (1 << j)] = std::max(f[i + (1 << j)], f[i] + (t + 1) * a[j] + b[j]);
}
}
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie();
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
G. Lpl and Energy-saving Lamps
题意:\(n\)个数,每天你会获得\(m\)块钱,你每天从左往右买东西,遇到能买的就买。\(q\)次询问,每次求前\(i\)天买了多少东西,以及第\(i\)天剩下多少钱。
\(d\)最大只有\(100000\),模拟就行。每天一直找最前的小于等于当前余额的位置,知道买不了任何东西,可以用线段树二分找。买掉的东西赋值为正无穷就行,这样每个位置最多被买一次。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 1e5 + 5, inf = 1e9;
int a[N];
struct SegmentTree {
struct Node {
int l, r;
int min;
};
std::vector<Node> tr;
SegmentTree(){};
SegmentTree(int n) {
tr.assign(n << 2, {});
build(0, n - 1);
}
void pushup(int u) {
tr[u].min = std::min(tr[u << 1].min, tr[u << 1 | 1].min);
}
void build(int l, int r, int u = 1) {
tr[u] = {l, r, inf};
if (l == r) {
tr[u].min = a[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 v) {
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].min = v;
u >>= 1;
while (u) {
pushup(u);
u >>= 1;
}
}
int query(int v) {
int u = 1;
if (tr[u].min > v) {
return -1;
}
while (tr[u].l != tr[u].r) {
if (tr[u << 1].min <= v) {
u = u << 1;
} else {
u = u << 1 | 1;
}
}
return tr[u].l;
}
};
void solve() {
int n, m;
std::cin >> n >> m;
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
SegmentTree tr(n);
const int D = 100000;
std::vector<int> cnt(D + 1), sum(D + 1);
for (int i = 1, tot = 0; i <= D; ++ i) {
tot += m;
cnt[i] = cnt[i - 1];
int p = tr.query(tot);
while (p != -1) {
++ cnt[i];
tr.modify(p, inf);
tot -= a[p];
p = tr.query(tot);
}
sum[i] = tot;
}
int q;
std::cin >> q;
while (q -- ) {
int d;
std::cin >> d;
std::cout << cnt[d] << " " << sum[d] << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie();
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
I. Skr
题意:求数字串的不同回文子串代表的数字的和。
一个串的不同的回文子串不超过串的长度。
所以我们可以马拉车求出所以回文中心和回文半径,然后对于每个回文从大到小判断,一直到出现过那么再缩小的回文子串也出现过。卡\(set\),需要手写\(hash\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int mod = 1e9 + 7;
const int N = 2e6 + 5, M = 2000003;
struct Hash {
std::array<int, M> head;
std::array<int, N> key, next;
int idx;
Hash() {
init();
}
void init() {
idx = 0;
std::fill(head.begin(), head.end(), -1);
}
bool insert(int s, int v) {
for (int i = head[s]; i != -1; i = next[i]) {
if (key[i] == v) {
return false;
}
}
key[idx] = v;
next[idx] = head[s];
head[s] = idx ++ ;
return true;
}
}hash;
std::vector<int> manacher(std::string s) {
std::string t = "#";
for (auto c : s) {
t += c;
t += '#';
}
int n = t.size();
std::vector<int> r(n);
for (int i = 0, j = 0; i < n; i++) {
if (2 * j - i >= 0 && j + r[j] > i) {
r[i] = std::min(r[2 * j - i], j + r[j] - i);
}
while (i - r[i] >= 0 && i + r[i] < n && t[i - r[i]] == t[i + r[i]]) {
r[i] += 1;
}
if (i + r[i] > j + r[j]) {
j = i;
}
}
return r;
}
void solve() {
std::string s;
std::cin >> s;
int n = s.size();
std::vector<int> h(n + 1), p(n + 1);
std::vector<int> h1(n + 1), p1(n + 1);
p[0] = 1;
for (int i = 0; i < n; ++ i) {
h[i + 1] = ((i64)h[i] * 10 + s[i] - '0') % mod;
p[i + 1] = (i64)p[i] * 10 % mod;
}
p1[0] = 1;
for (int i = 0; i < n; ++ i) {
h1[i + 1] = ((i64)h1[i] * 131 + s[i] - '0') % M;
p1[i + 1] = (i64)p1[i] * 131 % M;
}
auto get = [&](int l, int r) -> int {
return (h[r] - (i64)h[l - 1] * p[r - l + 1] % mod + mod) % mod;
};
auto get1 = [&](int l, int r) -> int {
return (h1[r] - (i64)h1[l - 1] * p1[r - l + 1] % M + M) % M;
};
auto R = manacher(s);
int ans = 0;
for (int i = 0; i < R.size(); ++ i) {
int l, r;
if (i & 1) {
l = i / 2 - R[i] / 2 + 1, r = i / 2 + R[i] / 2 - 1;
} else {
l = i / 2 - R[i] / 2, r = i / 2 + R[i] / 2 - 1;
}
++ l, ++ r;
while (l <= r) {
int v = get(l, r), v1 = get1(l, r);
if (!hash.insert(v1, v)) {
break;
}
ans = (ans + v) % mod;
++ l, -- r;
}
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie();
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
J. Sum
题意:\(f_i\)表示\(i\)可以表示为\(i = ab\)的数量,其中\(a, b\)没有一个质因子出现两次以上。求\(\sum_{j=1}^{i} f_j\)。
可以筛法直接求,\(f[1] = 1, f[p] = 2\)其中\(p\)是质数。考虑\(p*i\)怎么算,如果\(p\)是\(i\)的最小质因子,那么看是否在\(p*i\)里出现超过三次,如果超过那么\(a, b\)里一定有一个有两个\(p\),所以\(f_{p*i} = 0\),否则我们把\(p\)这个因子都拿掉,那么对于\(\frac{i}{p}\)的每个方案,让\(a,b\)都乘上\(p\)就是\(p*i\)的方案,所以\(f[p*i] = f[i/p]\)。否则\(p\)不是\(i\)的质因子,那么\(f[p*i] = f[i] * 2\),意味\(p\)可以放在\(i\)的每个方案的\(a, b\)任意一个身上,这样每个方案就会产生两个方案。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
std::vector<int> primes, minp;
std::vector<i64> f;
void sieve(int n) {
primes.clear();
minp.assign(n + 1, 0);
f.assign(n + 1, 0);
f[1] = 1;
for (int i = 2; i <= n; ++ i) {
if (minp[i] == 0) {
minp[i] = i;
f[i] = 2;
primes.push_back(i);
}
for (auto & p : primes) {
if (p * i > n) {
break;
}
minp[p * i] = p;
if (minp[i] == p) {
if (i / p % p == 0) {
f[p * i] = 0;
} else {
f[p * i] = f[i / p];
}
break;
}
f[p * i] = f[i] * f[p];
}
}
for (int i = 1; i <= n; ++ i) {
f[i] += f[i - 1];
}
}
void solve() {
int n;
std::cin >> n;
std::cout << f[n] << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie();
int t = 1;
sieve(2e7);
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
L. Magical Girl Haze
题意:一个图,可以最多使\(k\)条边边权为\(0\),求最短路。
分层图最短路,记\(dist[i][j]\)为到第\(i\)个点免费了\(j\)条边的最短路,跑\(dijkstra\)即可。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 1e5 + 5;
i64 dist[N][11];
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::vector<std::pair<int, int>>> adj(n);
for (int i = 0; i < m; ++ i) {
int u, v, w;
std::cin >> u >> v >> w;
-- u, -- v;
adj[u].emplace_back(v, w);
}
memset(dist, 0x3f, sizeof dist);
using A = std::tuple<i64, int, int>;
std::priority_queue<A, std::vector<A>, std::greater<A>> heap;
dist[0][0] = 0;
heap.emplace(0, 0, 0);
while (heap.size()) {
auto [d, u, t] = heap.top(); heap.pop();
if (dist[u][t] != d) {
continue;
}
for (auto & [v, w] : adj[u]) {
if (dist[v][t] > dist[u][t] + w) {
dist[v][t] = dist[u][t] + w;
heap.emplace(dist[v][t], v, t);
}
if (t + 1 <= k && dist[v][t + 1] > dist[u][t]) {
dist[v][t + 1] = dist[u][t];
heap.emplace(dist[v][t + 1], v, t + 1);
}
}
}
i64 ans = 1e18;
for (int i = 0; i <= k; ++ i) {
ans = std::min(ans, dist[n - 1][i]);
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie();
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}