VP Educational Codeforces Round 43 (Rated for Div. 2)


A. Minimum Binary Number

题意:给你一个01串,你每次可以交换相邻的两个元素,或者把两个相邻的1变成一个1。求二进制表示小最小的数。

显然我们可以把1消除到只剩一个。那么答案就是一个1加原串的所有0.
要特判原串只有一个0的情况。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
    if (n == 1) {
    	std::cout << s << "\n";
    	return;
    }
    int cnt = std::ranges::count(s, '0');
    std::string ans = "1" + std::string(cnt, '0');
    std::cout << ans << "\n";
}

B. Lara Croft and the New Game

题意:\(n\times m\)的矩阵,起点为\((1, 1)\)。按如下规则移动,先移动到\(n, 1\),然后移动到\(n, m\),之后按\(S\)型移动,也就是先移动到\(n - 1, m\),然后向左到\(n - 1, 2\),然后移动到\(n - 2, 2\),然后向右移动到\(n - 2, m\)。如此左右切换。移动一格算一步,求移动\(k\)步后到了哪里。

分两部分判断,一部分是还没有进行\(S\)型移动,也就是\(k \leq n + m - 2\)
否则求出移动了多少行,也就是\(\lceil \frac{k - (n + m - 2)}{m - 1} \rceil\),根据奇偶性得到接下来向左还是向右,然后剩下的步数就是\((k - (n + m - 2)) \% (m - 1)\)

点击查看代码
void solve() {
    i64 n, m, k;
    std::cin >> n >> m >> k;
    if (k <= n + m - 2) {
    	if (k <= n - 1) {
    		std::cout << 1 + k << " " << 1 << "\n";
     	} else {
     		std::cout << n << " " << k - n + 2 << "\n";
     	}
     	return;
    }

    k -= n + m - 2;
    int r = (k + m - 2) / (m - 1), c = k % (m - 1) == 0 ? m - 1 : k % (m - 1);
    if (r & 1) {
    	std::cout << n - r << " " << m - c + 1 << "\n";
    } else {
    	std::cout << n - r << " " << 1 + c << "\n";
    }
}

C. Nested Segments

题意:给你\(n\)个区间,求两个区间使得一个区间被另一个区间完全包含。

先按右端点排序,那么我们从前往后枚举,前面的区间的右端点都比当前端点小。那么我们只需要中一个左端点比当前大的进行,可以用\(set\)存前面的左端点,然后二分。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<std::array<int, 3>> a(n);
    for (int i = 0; i < n; ++ i) {
    	int l, r;
    	std::cin >> l >> r;
    	a[i] = {r, l, i};
    }

    std::ranges::sort(a, [&](std::array<int, 3> & a, std::array<int, 3> & b) {
    	if (a[0] == b[0]) {
    		return a[1] > b[1];
    	}
    	return a[0] < b[0];
    });

    std::set<std::pair<int, int>> s;
    for (auto & [r, l, id] : a) {
    	auto it = s.lower_bound({l, 0});
    	if (it != s.end()) {
    		std::cout << it->second + 1 << " " << id + 1 <<  "\n";
    		return;
    	}

    	s.insert({l, id});
    }

    std::cout << -1 << " " << -1 << "\n";
}

D. Degree Set

题意:给你一个递增序列\(d\),你要构造一个有\(d_n + 1\)个点的图,使得这些点的度数集合是\(d\)

对于一个\(n\)个点的图,如果我们给\([1, i]\)的点给其它点都连边,那么\([1, i]\)的点的度数都是\(n - 1\),其它点的度数是\(i\)
那么我们可以先给\([1, d_1]\)\([1, d_n]\)其它点都连边,然后给\([d_1 + 1, d_2]\)的点\([d_1 + 1, d_{n-1}+1]\)的点都连边,给\([d_{i-1} + 1, d_i]\)\([d_{i} + 1, d_{n-i+1}+1]\)的连边,就正好满足条件。因为第\(i\)部分的每个点有\(n - d_{n-i+1} - 1\)个没和它连边,度数恰好是\(d{n-i+1}\)

点击查看代码
void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n + 1);
	for (int i = 1; i <= n; ++ i) {
		std::cin >> a[i];
	}

	int l = 1, r = n;
	std::vector<std::pair<int, int>> ans;
	while (l <= r) {
		for (int i = a[l - 1] + 1; i <= a[l]; ++ i) {
			for (int j = i + 1; j <= a[r] + 1; ++ j) {
				ans.emplace_back(i, j);
			}
		}

		++ l;
		-- r;
	}

	std::cout << ans.size() << "\n";
	for (auto & [u, v] : ans) {
		std::cout << u << " " << v << "\n";
	}
}

E. Well played!

题意:有\(n\)个人,每个有生命值和攻击力,你可以进行\(a\)次操作把某个人的生命值乘二,可以进行\(b\)次操作把攻击力的值变成生命值的值。问最终攻击力最大和。

我们把第一类操作都给一个人值最优的。因为如果一个人生命值乘二然后赋值给攻击力更优,那么下一次再给他乘二,它也是最优的。
那么我们枚举第一类操作给谁,然后选\(k-1\)个人把生命值赋值给攻击力,这个可以用\(set\)维护。

点击查看代码
void solve() {
    int n, m, k;
    std::cin >> n >> m >> k;
    k = std::min(n, k);
    i64 sum = 0;
    std::vector<std::array<int, 3>> a(n);
    for (int i = 0; i < n; ++ i) {
    	int h, d;
    	std::cin >> h >> d;
    	a[i] = {h - d, h, d};
    	sum += d;
    }

    if (k == 0) {
    	std::cout << sum << "\n";
    	return;
    }  

    std::ranges::sort(a, std::greater<>());
    std::multiset<int> s;
    for (int i = 0; i < k; ++ i) {
    	if (a[i][0] > 0) {
    		s.insert(a[i][0]);
    		sum += a[i][0];
    	}
    }


    i64 ans = sum;
    for (int i = 0; i < n; ++ i) {
    	if (a[i][0] > 0 && i < k) {
    		s.extract(a[i][0]);
    		sum -= a[i][0];
    	}

    	i64 v = (1ll << m) * a[i][1] - a[i][2];
    	if (s.size() == k) {
    		ans = std::max(ans, sum - *s.begin() + v);
    	} else {
    		ans = std::max(ans, sum + v);
    	}

	 	if (a[i][0] > 0 && i < k) {
    		s.insert(a[i][0]);
    		sum += a[i][0];
    	}   	
    }

    std::cout << ans << "\n";
}

F. Minimal k-covering

题意:给你一个二分图,对于每个\(k, k \in [0, minDegree]\),其中\(minDegree\)是顶点度数的最小值,你要选一些边,使得每个点的度数至少为\(k\),且边数最少。

考虑网络流,如果我们把至少选\(k\)条边当成给每个点\(k\)的流量,是不行的,因为可能需要某个左边点多出边给右边的点,才能使得右边点出\(k\)条边。
我们可以把选至少\(k\)条边,变成最多选\(d_i - k\)条边,其中\(d_i\)\(i\)度数。那么选出来的边就是不要的边,我们希望不要的边最多,则可以跑网络流。
然后一个经典套路是我们可以利用残余网络,而不是每次都建图重新跑。发现\(k\)的方案是\(k + 1\)的方案减少一些边过来的,那么我们可以从大到小跑网络流,每次给边多加1的流量就行。

点击查看代码
constexpr int inf = 1E9;
template<class T>
struct MaxFlow {
    struct _Edge {
        int to;
        T cap;
        _Edge(int to, T cap) : to(to), cap(cap) {}
    };
    
    int n;
    std::vector<_Edge> e;
    std::vector<std::vector<int>> g;
    std::vector<int> cur, h;
    
    MaxFlow() {}
    MaxFlow(int n) {
        init(n);
    }
    
    void init(int n) {
        this->n = n;
        e.clear();
        g.assign(n, {});
        cur.resize(n);
        h.resize(n);
    }
    
    bool bfs(int s, int t) {
        h.assign(n, -1);
        std::queue<int> que;
        h[s] = 0;
        que.push(s);
        while (!que.empty()) {
            const int u = que.front();
            que.pop();
            for (int i : g[u]) {
                auto [v, c] = e[i];
                if (c > 0 && h[v] == -1) {
                    h[v] = h[u] + 1;
                    if (v == t) {
                        return true;
                    }
                    que.push(v);
                }
            }
        }
        return false;
    }
    
    T dfs(int u, int t, T f) {
        if (u == t) {
            return f;
        }
        auto r = f;
        for (int &i = cur[u]; i < int(g[u].size()); ++i) {
            const int j = g[u][i];
            auto [v, c] = e[j];
            if (c > 0 && h[v] == h[u] + 1) {
                auto a = dfs(v, t, std::min(r, c));
                e[j].cap -= a;
                e[j ^ 1].cap += a;
                r -= a;
                if (r == 0) {
                    return f;
                }
            }
        }
        return f - r;
    }
    void addEdge(int u, int v, T c) {
        g[u].push_back(e.size());
        e.emplace_back(v, c);
        g[v].push_back(e.size());
        e.emplace_back(u, 0);
    }
    T flow(int s, int t) {
        T ans = 0;
        while (bfs(s, t)) {
            cur.assign(n, 0);
            ans += dfs(s, t, std::numeric_limits<T>::max());
        }
        return ans;
    }
    
    std::vector<int> get(int n1, int n2) {
    	std::vector<int> res;
    	for (int u = 0; u < n1; ++ u) {
    		for (auto & i : g[u]) {
    			if (e[i].to >= n1 && e[i].to < n1 + n2 && e[i].cap != 0) {
    				res.push_back(i / 2 + 1 - n1 - n2);
    			}
    		}
    	}

    	return res;
    };

    void addS(int s, T w) {
    	for (auto & i : g[s]) {
    		e[i].cap += w;
    	}
    }

    void addT(int t, T w) {
    	for (auto & i : g[t]) {
    		e[i ^ 1].cap += w;
    	}
    }
};

void solve() {
    int n1, n2, m;
    std::cin >> n1 >> n2 >> m;
    std::vector<std::pair<int, int>> edges(m);
    std::vector<int> d(n1 + n2);
    for (int i = 0; i < m; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	edges[i] = {u, v};
    	++ d[u];
    	++ d[v + n1];
    }

    int k = m;
    for (int i = 0; i < n1 + n2; ++ i) {
    	k = std::min(k, d[i]);
    }

    MaxFlow<int> f(n1 + n2 + 2);
    int s = n1 + n2, t = n1 + n2 + 1;
    for (int i = 0; i < n1; ++ i) {
    	f.addEdge(s, i, d[i] - k);
    }

    for (int i = 0; i < n2; ++ i) {
    	f.addEdge(i + n1, t, d[i + n1] - k);
    }

    for (auto & [u, v] : edges) {
    	f.addEdge(u, v + n1, 1);
    }

    std::vector<std::vector<int>> ans(k + 1);
    for (int i = k; i >= 0; -- i) {
    	f.flow(s, t);
    	ans[i] = f.get(n1, n2);
    	f.addS(s, 1); f.addT(t, 1);
    }

    for (int i = 0; i <= k; ++ i) {
    	std::cout << ans[i].size();
    	for (auto & x : ans[i]) {
    		std::cout << " " << x;
    	}
    	std::cout << "\n";
    }
}
posted @ 2025-03-27 16:26  maburb  阅读(17)  评论(0)    收藏  举报