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


A. Vasya and Book

点击查看代码
void solve() {
    int n, x, y, d;
    std::cin >> n >> x >> y >> d;	
    if (std::abs(x - y) % d) {
    	int ans = 1e9;
    	if ((y - 1) % d == 0) {
    		ans = std::min(ans, (x + d - 1) / d + (y - 1) / d);
    	}

    	if ((n - y) % d == 0) {
    		ans = std::min(ans, (n - x + d - 1) / d + (n - y) / d);
    	}

    	if (ans == 1e9) {
    		std::cout << -1 << "\n";
    	} else {
    		std::cout << ans << "\n";
    	}
    } else {
    	std::cout << std::abs(x - y) / d << "\n";
    }
}

B. Vova and Trophies

题意:给你一个只包含两种字符的串,操作恰好一次:交换两个字符。使得连续的G最长。

二分长度,然后看前缀和里是不是G的数量大于等于\(len\)或者等于\(len-1\)且外面还有G。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
    int sum = std::ranges::count(s, 'G');
    std::vector<int> pre(n + 1);
    for (int i = 0; i < n; ++ i) {
    	pre[i + 1] = pre[i] + (s[i] == 'G');
    }

    auto check = [&](int len) -> bool {
    	for (int i = 1; i + len - 1 <= n; ++ i) {
    		int cnt = pre[i + len - 1] - pre[i - 1];
    		if (cnt >= len || cnt == len - 1 && cnt < sum) {
    			return true;
    		}
    	}
    	return false;
    };

    int l = 0, r = n;
    while (l < r) {
    	int mid = l + r + 1 >> 1;
    	if (check(mid)) {
    		l = mid;
    	} else {
    		r = mid - 1;
    	}
    }

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

C. Multi-Subject Competition

题意:\(n\)个人,\(m\)个类型,第\(i\)个人类型为\(s_i\),能力为\(r_i\)。你需要选一些人出来,使得选出来的每种类型的人数相同。求能力总和最大。

每个类型的人搞个前缀和\(sum[i]\),然后依次把\(sum[i][j]\)加到\(ans[j]\)里。那么答案直接枚举得到最大的\(ans\)

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<std::vector<int>> a(m);
    for (int i = 0; i < n; ++ i) {
    	int s, r;
    	std::cin >> s >> r;
    	-- s;
    	a[s].push_back(r);
    }

    std::vector<int> sum(n);
    for (int i = 0; i < m; ++ i) {
    	std::ranges::sort(a[i], std::greater<int>());
    	for (int j = 0, pre = 0; j < a[i].size(); ++ j) {
    		pre += a[i][j];
    		sum[j] += std::max(0, pre);
    	}
    }

    int ans = 0;
    for (int i = 0; i < n; ++ i) {
    	ans = std::max(ans, sum[i]);
    }

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

D. Maximum Diameter Graph

题意:构造一个图,使得每个点的度数小于等于\(a_i\)。且直径最大。

先构造直径,显然是一条链。把所有\(a_i \geq 2\)的点连到一起,所有\(a_i = 1\)的点,分两个到两端,然后剩下的和其它点连就行了。看能不能连完。

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

    std::vector<int> b, c;
    for (int i = 0; i < n; ++ i) {
    	if (a[i] >= 2) {
    		b.push_back(i);
    	} else {
    		c.push_back(i);
    	}
    }

    if (b.empty()) {
    	std::cout << "NO\n";
    	return;
    }

    std::vector<std::pair<int, int>> ans;
    std::vector<int> d(n);
    for (int i = 0; i + 1 < b.size(); ++ i) {
    	ans.emplace_back(b[i], b[i + 1]);
    	++ d[b[i]];
    	++ d[b[i + 1]];
    }

    int ans_len = (int)b.size() - 1;
    if (c.size()) {
    	ans.emplace_back(b.back(), c.back());
    	++ d[c.back()];
    	++ d[b.back()];
    	c.pop_back();
    	++ ans_len;
    }

    if (c.size()) {
    	++ ans_len;
    }

    for (int i = 0; i < b.size() && c.size(); ++ i) {
    	while (d[b[i]] < a[b[i]] && c.size()) {
    		ans.emplace_back(b[i], c.back());
	    	++ d[c.back()];
    		++ d[b[i]];
    		c.pop_back();
    	}
    }

    if (c.size()) {
    	std::cout << "NO\n";
    	return;
    } else {
    	std::cout << "YES " << ans_len << "\n";
    	std::cout << ans.size() << "\n";
    	for (auto & [u, v] : ans) {
    		std::cout << u + 1 << " " << v + 1 << "\n";
    	}
    }
}

E. Increasing Frequency

题意:给你一个长度为\(n\)的数组\(a\)和一个数字\(c\)。你要恰好操作一个区间\([l, r]\)把其中所有数加上某个数。求\(c\)的最大数量。

其实就是找一个区间,把区间里某一类数的个数减去区间\(c\)出现的个数,求最大。
如果我们记\(sum[x][i]\)为数字\(x\)\([1, i]\)里出现的次数,那么对于\(i\)这个位置,我们肯定要找一个\(j, (j < i)\)使得\(a_j = x\)。因为如果左端点不是\(x\)显然可以截掉。那么这个位置的最大值是\(\max_{j, a_j = x} sum[x][i] - sum[x][j - 1] - (sum[c][i] - sum[c][j - 1])\),得到\(sum[x][i] - sum[c][i] + (sum[c][j - 1] - sum[x][j - 1])\)。发现\(i, j\)可以拆开,那么我们只需要记录\(sum[c][j - 1] - sum[c][j - 1]\)的最大值就行。于是也不用真的开二维数组记录前缀和。

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

    std::vector<int> pre(n + 1);
    for (int i = 1; i <= n; ++ i) {
    	pre[i] = pre[i - 1] + (a[i] == c);
    }

    std::vector<int> max(N), cnt(N);
    int ans = 0;
   	for (int i = 1; i <= n; ++ i) {
   		max[a[i]] = std::max(max[a[i]], pre[i - 1] - cnt[a[i]]);
   		++ cnt[a[i]];
   		ans = std::max(ans, cnt[a[i]] - pre[i] + max[a[i]]);
   	} 

   	std::cout << std::ranges::count(a, c) + ans << "\n";
}

F. Speed Dial

题意:给你\(n\)个数字串,每个数字串需要出现\(m_i\)次。你可以选择\(k\)个数字串,每次可以不费代价选择其中一个(也可以选择空串),使得当前的数字串等于它,然后每次你可以花费\(1\)的代价把任意一个字符接到后面。如果当前串等于\(n\)个数字串其中一个,你可以使这个数字串出现次数加一,然后你的数字串会变成空串。求所有数字串满足条件的最小代价。

我们建一个\(trie\),叶子的值为以他结尾的串的\(m\)的和。那么问题变成,在\(trie\)上选择\(k + 1\)个节点,其中根节点必选。每个叶子节点的代价为到最近的一个被选定节点的距离$\times $ 这棵叶子的值。总代价就是叶子代价的和。求总代价最小。

考虑用\(dp\)求解,记\(f[u][i][j]\)\(u\)这个节点距离最近的被选择的点距离为\(i\)\(u\)这棵子树已经选了\(j\)个节点的最小代价。如果\(i=0\)代表选择了\(u\),这时\(j \geq 1\)。那么叶子节点初始化是:如果选择这个点\(f[u][0][1] = 0\),否则枚举\(i\)\(f[u][i][0] = i\times val_u\)
对于非叶子节点,\(f[u][i][j] += \min_{x = 0}^{k+1} \min(f[u][i][j - x] + f[v][i + 1][x], f[u][i][j-x] + f[v][0][x])\),意味\(v\)这个子节点选了\(x\)个,是不是和\(u\)选同一个节点,或者选择自己。
注意一些细节就行。

点击查看代码
const int TrieN = 505;
int trie[TrieN][10];
int node_val[TrieN];
struct Trie {
	int idx;
	Trie() {
		idx = 0;
		newNode();
	}

	int newNode() {
		memset(trie[idx], 0, sizeof trie[idx]);
		node_val[idx] = 0;
		return idx ++ ;
	}

	void insert(std::string s, int val) {
		int p = 0;
		for (auto & c : s) {
			int x = c - '0';
			if (!trie[p][x]) {
				trie[p][x] = newNode();
			}

			p = trie[p][x];
		}
		node_val[p] += val;
	}

	int dp(int k) {
		std::vector f(idx, std::vector(idx, std::vector<int>(k + 1)));
		auto dfs = [&](auto & self, int u, int dep) -> void {
			f[u][0][1] = 0;
			for (int i = 1; i <= dep; ++ i) {
				f[u][i][0] = i * node_val[u];
			}

			for (int i = 0; i <= 9; ++ i) {
				int v = trie[u][i];
				if (!v) {
					continue;
				}

				self(self, v, dep + 1);

				for (int j = 0; j <= dep; ++ j) {
					for (int l = k; l >= !j; -- l) {
						int min = 1e9;
						for (int r = 0; r <= l - !j; ++ r) {
							min = std::min(min, f[u][j][l - r] + f[v][j + 1][r]);
							if (r > 0) {
								min = std::min(min, f[u][j][l - r] + f[v][0][r]);
							}
						}

						f[u][j][l] = min;
					}
				}
			}
		};

		dfs(dfs, 0, 0);

		return f[0][0][k];
	}
};

void solve() {
    int n, k;
    std::cin >> n >> k;
    Trie tr;
    for (int i = 0; i < n; ++ i) {
    	std::string s;
    	int m;
    	std::cin >> s >> m;
    	tr.insert(s, m);
    }

    std::cout << tr.dp(k + 1) << "\n";
}

G. Petya and Graph

题意:给你一个图,选择其中一个子图,其价值为边权减点权。如果一条边在子图里,那么它的两个端点也在子图里。

最大权闭合子图板子。
算法:原图边容量为正无穷,虚拟源点向正权点连边,负权点向虚拟汇点连边,容量为点权绝对值
最大权闭合图 = 所有正权点之和 – 最小割

点击查看代码
constexpr i64 inf = 1E18;
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<bool> minCut() {
        std::vector<bool> c(n);
        for (int i = 0; i < n; i++) {
            c[i] = (h[i] != -1);
        }
        return c;
    }
    
    struct Edge {
        int from;
        int to;
        T cap;
        T flow;
    };
    std::vector<Edge> edges() {
        std::vector<Edge> a;
        for (int i = 0; i < e.size(); i += 2) {
            Edge x;
            x.from = e[i + 1].to;
            x.to = e[i].to;
            x.cap = e[i].cap + e[i + 1].cap;
            x.flow = e[i + 1].cap;
            a.push_back(x);
        }
        return a;
    }
};

void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    const i64 inf = std::numeric_limits<i64>::max();

    MaxFlow<i64> f(n + m + 2);
    int s = n + m, t = n + m + 1;
    for (int i = 0; i < n; ++ i) {
    	f.addEdge(i, t, a[i]);
    }

    i64 sum = 0;
    for (int i = 0; i < m; ++ i) {
    	int u, v, w;
    	std::cin >> u >> v >> w;
    	-- u, -- v;
    	f.addEdge(s, n + i, w);
    	f.addEdge(n + i, u, inf);
    	f.addEdge(n + i, v, inf);
    	sum += w;
    }

    std::cout << sum - f.flow(s, t) << "\n";
}
posted @ 2025-04-09 12:19  maburb  阅读(9)  评论(0)    收藏  举报