做题记录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, j} |= f_{i - 1, j} \]

\[f_{i, (j + a_i) \mod m} |= f_{i - 1, 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";
}
posted @ 2025-10-03 10:29  dbywsc  阅读(12)  评论(0)    收藏  举报