abc210
C - Colorful Candies 359
固定大小滑动窗口内不同颜色数
D - National Railway 1507
给定矩阵 \(a\) 和数 \(c\),任选不重合的两点 \((i,j),(i',j')\),求 \(a_{i,j} + a_{i',j'} + c(|i-i'|+|j-j'|)\) 的最小值
拆绝对值,从左上到右下更新一遍,再从右上到左下更新一遍
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
ll n, m, c;
cin >> n >> m >> c;
vector<vector<ll>> a(n, vector<ll>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++)
cin >> a[i][j];
}
ll ans = 1e15;
vector<vector<ll>> f(n, vector<ll>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
f[i][j] = a[i][j] - c * (i + j);
if (i) {
ans = min(ans, a[i][j] + c * (i + j) + f[i - 1][j]);
f[i][j] = min(f[i][j], f[i - 1][j]);
}
if (j) {
ans = min(ans, a[i][j] + c * (i + j) + f[i][j - 1]);
f[i][j] = min(f[i][j], f[i][j - 1]);
}
}
}
vector<vector<ll>> g(n, vector<ll>(m));
for (int i = 0; i < n; i++) {
for (int j = m - 1; j >= 0; j--) {
g[i][j] = a[i][j] - c * (i - j);
if (i) {
ans = min(ans, a[i][j] + c * (i - j) + g[i - 1][j]);
g[i][j] = min(g[i][j], g[i - 1][j]);
}
if (j < m - 1) {
ans = min(ans, a[i][j] + c * (i - j) + g[i][j + 1]);
g[i][j] = min(g[i][j], g[i][j + 1]);
}
}
}
cout << ans;
return 0;
}
E - Ring MST 1618
Kruskal最小生成树 + 数论(裴蜀定理)妙妙题!
有 \(n(1e9)\) 个孤立点 \(0\sim n-1\) 和 \(1e5\) 种可选操作。第 \(i\) 种操作任选一点 \(x\),花费 \(c_i\) 连接 \(x\) 和 \(x+a_i\pmod n\)。每周操作次数不限,顺序任意。问把所有点连通的最小花费
按花费从小到大考虑每一种操作,用当前操作连尽可能多的边
问题是,怎么知道当前操作能新增多少条边?记用前 \(i\) 种操作最多能连 \(f_i\) 条边,那么操作 \(i\) 应该连的边数就是 \(f_i-f_{i-1}\)
用前 \(i\) 种操作使某两点 \(u,v\) 连通,当且仅当 \(u\equiv v + k_1a_1+k_2a_2+\cdots +k_ia_i\pmod n\),也就是 \(u=v+k_0n+k_1a_1+k_2a_2+\cdots +k_ia_i=v + k\gcd(n,a_1,a_2,\cdots,a_i)\)
也就是说前 \(i\) 种操作后,连通块数量是 \(\gcd(n,a_1,a_2,\cdots,a_i)\)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ll n, m;
cin >> n >> m;
vector<pair<ll, ll>> e(m);
for (auto &[c, a] : e) {
cin >> a >> c;
}
sort(e.begin(), e.end());
ll ans = 0;
for (auto [c, a] : e) {
ll g = __gcd(n, a); // n g 旧、新连通块数量
ans += (n - g) * c;
n = g;
}
cout << (n == 1 ? ans : -1);
return 0;
}
F - Coprime Solitaire 2632
\(3e4\) 张牌,每张牌正反面写有 \(2e6\) 内的正整数,问能否使每张牌朝上的面的数字两两互质。
前缀优化建图 + 2-sat
对每个质数 \(p\),把 \(p\) 的倍数都放入集合 \(mp_p\),那么 \(\forall i,j\in mp_p\) 连边 \(i\to \neg j, j\to \neg i\),边数太多,用前缀优化建图
最后看每张牌的正反面在不在同一连通块中即可
#include <bits/stdc++.h>
using namespace std;
// 不太清楚点数会到多少所以全开的vector...
vector<vector<int>> G;
vector<int> dfn, low, instk, scc;
stack<int> stk;
int tot, cnt; //均是1-index
void tarjan(int u) {
dfn[u] = low[u] = ++tot;
stk.push(u); instk[u] = true;
for (int v : G[u]) {
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (instk[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) {
++cnt; int v;
do {
v = stk.top(); stk.pop(); instk[v] = false;
scc[v] = cnt;
} while (v != u);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
//筛每个数的最小质因子
vector<int> primes;
const int M = 2e6 + 5;
vector<int> minp(M);
for (int i = 2; i < M; i++) {
if (!minp[i]) primes.push_back(i), minp[i] = i;
for (int p : primes) {
if (i * p > M) break;
minp[i * p] = p;
if (i % p == 0) break;
}
}
map<int, vector<int>> mp;
int n;
cin >> n;
for (int i = 0; i < n; i++) { //0号牌视为0,1
int x, y;
cin >> x >> y;
while (x > 1) {
int p = minp[x];
mp[p].push_back(2 * i); //把正面放入集合
while (x % p == 0) x /= minp[x];
}
while (y > 1) {
int p = minp[y];
mp[p].push_back(2 * i + 1);
while (y % p == 0) y /= minp[y];
}
}
int idx = 2 * n; //用了[0,idx)这些点
G.resize(idx);
for (auto [p, s] : mp) {
for (int i = 0; i < s.size(); i++) {
G.emplace_back(vector<int>());
G[idx].push_back(s[i] ^ 1);
if (i) {
G[idx].push_back(idx - 1);
G[s[i]].push_back(idx - 1);
}
idx++;
}
for (int i = s.size() - 1; ~i; i--) {
G.emplace_back(vector<int>());
G[idx].push_back(s[i] ^ 1);
if (i != (int)s.size() - 1) {
G[idx].push_back(idx - 1);
G[s[i]].push_back(idx - 1);
}
idx++;
}
}
dfn.resize(idx); low.resize(idx); instk.resize(idx); scc.resize(idx);
for (int i = 0; i < 2 * n; i++) {
if (!dfn[i]) tarjan(i);
}
for (int i = 0; i < n; i++) {
if (scc[2 * i] == scc[2 * i + 1])
return cout << "No", 0;
}
cout << "Yes";
return 0;
}

浙公网安备 33010602011771号