做题记录4
CF577B.Modulo Sum
思路
求是否存在一段非空子序列的和模 \(m\) 的值为 \(0\) ,可以先等价地对每一个数字都模 \(m\) 。对于一个长度为 \(n\) 的序列,显然有 \(n\) 段前缀和,并且前缀和模 \(m\) 的值有 \([0, m)\) 共 \(m\) 种。根据抽屉原理,当 \(n > m\) 时,一定有至少两段前缀和的值是相同的,而这两段前缀和相间得到的这一段连续子序列的和模 \(m\) 的值是 \(0\) ,符合要求。
也就是说,当 \(n > m\) 时一定是 YES 。
当 \(n \leq m\) 时,这个问题就转化为了一个 01背包,由于 \(m \leq 10^3\) ,因此可以直接计算。
设 \(f_{i, j}\) 表示从第 \(i\) 个数开始,能否通过选取一些数使得这些数的和模 \(m\) 的值为 \(j\) ,就有状态转移方程:
考虑边界,有:\(f_{i, a_i} = 1\) 。
最后只需要判断是否存在一个 \(f_{i, 0} = true\) 就可以了。
代码
void solve(void) {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n + 1);
for(int i = 1; i <= n; i++) {
std::cin >> a[i];
a[i] %= m;
}
if(n > m) {
std::cout << "YES\n";
return;
}
std::vector<std::vector<int>> f(2020, std::vector<int>(2020));
for(int i = 1; i <= n; i++) f[i][a[i]] = 1;
for(int i = 1; i <= n; i++) {
for(int j = 0; j < m; j++) {
f[i][j] |= f[i - 1][j];
f[i][(j + a[i]) % m] |= f[i - 1][j];
}
if(f[i][0]) {std::cout << "YES\n"; return;}
}
std::cout << "NO\n";
}
CF25D. Roads not only in Berland
思路
操作数显然是联通块的数量减一。接下来考虑如何构造出一种输出方式。
可以用并查集维护这张图,一个联通块可以删边当且仅当联通块是一个环,构成环的这条边是多余的。所以记录一下每个联通块多出来的多余边,并且记录每个联通块的根节点,之后就可以按照下面的方式输出:
环边的两个节点 联通块的根和下个联通块的根连一条新边
代码
struct DSU {
std::vector<int> p, siz;
DSU(int n): p(n + 1), siz(n + 1, 1) {
std::iota(p.begin(), p.end(), 0);
}
int find(int x) {
if(x == p[x]) return p[x];
return p[x] = find(p[x]);
}
bool unite(int a, int b) {
int pa = find(a), pb = find(b);
if(pa == pb) return false;
if(siz[pa] < siz[pb]) std::swap(pa, pb);
p[pb] = pa;
siz[pa] += siz[pb];
return true;
}
bool same(int u, int v) {return find(u) == find(v);}
int size(int u) {return siz[find(u)];}
};
void solve(void) {
int n; std::cin >> n;
DSU dsu(n);
std::vector<PII> del;
for(int i = 1; i < n; i++) {
int u, v; std::cin >> u >> v;
if(!dsu.unite(u, v)) del.emplace_back(u, v);
}
std::vector<int> roots;
for(int i = 1; i <= n; i++) {
if(dsu.p[i] == i) {
roots.push_back(i);
}
}
std::cout << (int)roots.size() - 1 << '\n';
for(int i = 0; i < (int)roots.size() - 1; i++) {
int u = del[i].x, v = del[i].y;
int a = roots[i], b = roots[i + 1];
std::cout << u << ' ' << v << ' ' << a << ' ' << b << '\n';
}
}
CF1691D.Max GEQ Sum
思路
对于每一个 \(a_i\) ,用单调栈维护当它作为子数组时可以覆盖哪些范围,之后就可以用线段树查询这段范围内是否存在不合法的情况。
具体来说,为 \(a[1..n]\) 的前缀和维护线段树,对于每个 \(a_i\) ,如果它可以覆盖的区间 \([l_i, r_i]\) 中和最大的那一段减去和最小的那一段仍然至少存在 \(a_i\) 大小的和,就说明这一段不合法,直接输出 "No" 就可以了。
代码
struct SegTree {
int n;
const i64 INF = 4e18;
std::vector<i64> max, tag;
SegTree(int n_): n(n_) {
max.assign(4 * n, 0);
tag.assign(4 * n, 0);
}
void pull(int o) {
max[o] = std::max(max[o * 2], max[o * 2 + 1]);
}
void apply(int o, int l, int r, int v) {
tag[o] += v;
max[o] += v;
}
void push(int o, int l, int r) {
if(tag[o] != 0) {
int m = (l + r) / 2;
apply(o * 2, l, m, tag[o]);
apply(o * 2 + 1, m + 1, r, tag[o]);
tag[o] = 0;
}
}
void build(int o, int l, int r, const std::vector<i64> &a) {
tag[o] = 0;
if(l == r) {
max[o] = a[l];
return;
}
int m = (l + r) / 2;
build(o * 2, l, m, a);
build(o * 2 + 1, m + 1, r, a);
pull(o);
}
void build(const std::vector<i64> &a) {
build(1, 1, n, a);
}
i64 query(int o, int l, int r, int L, int R) {
if(R < l || r < L) return -INF;
if(L <= l && r <= R) return max[o];
push(o, l, r);
int m = (l + r) / 2;
return std::max(query(o * 2, l, m, L, R), query(o * 2 + 1, m + 1, r, L, R));
}
i64 query(int x, int y) {
return query(1, 1, n, x, y);
}
void add(int o, int l, int r, int L, int R, int v) {
if(R < l || r < L) return;
if(L <= l && r <= R) {
apply(o, l, r, v);
return;
}
push(o, l, r);
int m = (l + r) / 2;
add(o * 2, l, m, L, R, v);
add(o * 2 + 1, m + 1, r, L, R, v);
pull(o);
}
void add(int x, int y, int v) {
add(1, 1, n, x, y, v);
}
};
void solve(void) {
int n;
std::cin >> n;
std::vector<int> a(n + 1);
std::vector<i64> max(n + 2), min(n + 2), s(n + 1);
for(int i = 1; i <= n; i++) std::cin >> a[i];
for(int i = 1; i <= n; i++) {
s[i] = s[i - 1] + a[i];
}
for(int i = 0; i <= n; i++) {
max[i + 1] = s[i];
min[i + 1] = -s[i];
}
SegTree Smax(n + 1), Smin(n + 1);
Smax.build(max), Smin.build(min);
std::vector<int> l(n + 1), r(n + 1);
std::stack<int> stk;
for(int i = 1; i <= n; i++) {
while(stk.size() && a[stk.top()] < a[i]) {
r[stk.top()] = i - 1;
stk.pop();
}
if(stk.size()) l[i] = stk.top();
else l[i] = 0;
stk.push(i);
}
while(stk.size()) r[stk.top()] = n, stk.pop();
for(int i = 1; i <= n; i++) {
i64 max_ps = Smax.query(i + 1, r[i] + 1);
i64 min_ps = -Smin.query(l[i] + 1, i);
if(max_ps - min_ps > a[i]) {
std::cout << "No\n";
return;
}
}
std::cout << "Yes\n";
}

浙公网安备 33010602011771号