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;
}
posted @ 2025-05-21 22:24  maburb  阅读(15)  评论(0)    收藏  举报