VP Educational Codeforces Round 2


A. Extract Numbers

题意:一个字符串只包含数字小写字母和';', 以及','。由';'和','对字符串进行分隔。你要判断每个分隔的部分是不是合法数字,是就加入到第一个序列里,否则加入第二个序列。然后把两个序列按要求输出。

模拟题,就是把每段取出来判断就行了。不过要注意仅有一个空字符串的情况也不算序列为空。

点击查看代码
void solve() {
    std::string s;
    std::cin >> s;
    int n = s.size();
    std::vector<std::string> a, b;
    for (int i = -1; i < n; ++ i) {
    	int j = i + 1;
    	bool flag = true;
    	std::string t;
    	while (j < n && s[j] != ';' && s[j] != ',') {
    		if (!isdigit(s[j])) {
    			flag = false;
    		}
    		t.push_back(s[j]);
    		++ j;
    	}

    	if (j == i + 1) {
    		b.push_back(",");
    	} else {
    		if (flag && (j == i + 2 || s[i + 1] != '0')) {
    			a.push_back(t + ",");
    		} else {
    			b.push_back(t + ",");
    		}
    	}

    	i = j - 1;
    }

    if (a.empty()) {
    	std::cout << "-\n";
    } else {
    	a.back().pop_back();
    	std::cout << "\"";
    	for (auto & s : a) {
    		std::cout << s;
    	}
    	std::cout << "\"\n";
    }

    if (b.empty()) {
    	std::cout << "-\n";
    } else {
    	b.back().pop_back();
    	std::cout << "\"";
    	for (auto & s : b) {
    		std::cout << s;
    	}
    	std::cout << "\"\n";
    }
}

B. Queries about less or equal elements

题意:给你两个数组a,b,求b中每个元素大于等于多少个a中的元素。

a排序后,遍历b的元素二分求答案。

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

    for (int j = 0; j < m; ++ j) {
    	std::cin >> b[j];
    }

    std::sort(a.begin(), a.end());
    for (int i = 0; i < m; ++ i) {
    	int res = std::upper_bound(a.begin(), a.end(), b[i]) - a.begin() + 1;
    	-- res;
    	std::cout << res << " \n"[i == m - 1];
    }
}

C. Make Palindrome

题意:给你一个字符串,你可以更改任意位置的字符,然后可以重新排序,使得字符串是回文串,求最小操作数的字典序最小的方案。

要操作数最小,那么肯定优先让每个字符和相同的两两匹配,那么每个种类的字符最多有一个匹配不上。
对于这些匹配不上的字符,因为答案要字典序最小,那么就让最大的变成最小的。

点击查看代码
void solve() {
    std::string s;
    std::cin >> s;
    std::vector<int> cnt(26);
    for (auto & c : s) {
    	++ cnt[c - 'a'];
    }

    for (int l = 0, r = 25; l < r;) {
    	while (l < r && cnt[l] % 2 == 0) {
    		++ l;
    	}

    	while (l < r && cnt[r] % 2 == 0) {
    		-- r;
    	}

    	if (l < r) {
    		cnt[l] += 1;
    		cnt[r] -= 1;
    	}
    }



    std::string ans;
    for (int i = 0; i < 26; ++ i) {
    	int n = cnt[i] / 2;
    	ans += std::string(n, i + 'a');
    }

    std::cout << ans;
	for (int i = 0; i < 26; ++ i) {
		if (cnt[i] % 2) {
			std::cout << (char)(i + 'a');
			break;
		}
	}

    std::reverse(ans.begin(), ans.end());
    std::cout << ans << "\n";
}

D. Area of Two Circles' Intersection

题意:求两个圆的面积交。

纯数学题,也是模板题。但我数学太差,只能看别人题解。
首先讨论两个圆是相交还是相离还是包含,如果相离则答案为0,如果包含则是小圆的面积。现在考虑相交的情况。
发现其交集是两个弓形的面积和,而每个弓形可以单独求,它就是对于圆里的一个扇形面积减去一个三角形面积。
那么可以根据余弦定理求出扇形的夹角,就可以计算这个弓形的面积了。
这题需要开long double

点击查看代码
#define double long double

void solve() {
    double x1, y1, r1, x2, y2, r2;
    std::cin >> x1 >> y1 >> r1 >> x2 >> y2 >> r2;
    double dx = x1 - x2, dy = y1 - y2;
    double d = std::sqrt(dx * dx + dy * dy);

    std::cout << std::fixed << std::setprecision(12);
    
    double ans = 0;
    if (d >= r1 + r2) {
    	std::cout << ans << "\n";
    	return;
    }

    if (d <= std::fabs(r1 - r2)) {
    	std::cout << std::min(r1, r2) * std::min(r1, r2) * std::acos(-1) << "\n";
    	return;
    }

    double angle1 = 2 * std::acos((r1 * r1 + d * d - r2 * r2) / (2 * r1 * d));
    double angle2 = 2 * std::acos((r2 * r2 + d * d - r1 * r1) / (2 * r2 * d));
    ans = r1 * r1 * (angle1 - std::sin(angle1)) / 2 + r2 * r2 * (angle2 - std::sin(angle2)) / 2;
    std::cout << ans << "\n";
}	

E. Lomsat gelral

题意:给你一棵树,每个数有颜色,求每个子树颜色数最多的颜色编号之和。

解法一:
线段树合并还是老早前学的,并且一直没用过,今天碰上没想到还能写出来。
每个点动态开点建线段树,然后把每棵子树合并起来。

点击查看代码
#define ls(u) (tr[u].lson)
#define rs(u) (tr[u].rson)

const int N = 1e5 + 5;

struct Node {
	int lson, rson;
	i64 max, sum;
}tr[N << 5];

int tot = 0;

void pushup(int u) {
	tr[u].max = std::max(tr[ls(u)].max, tr[rs(u)].max);
	tr[u].sum = 0;
	if (tr[ls(u)].max == tr[u].max) {
		tr[u].sum += tr[ls(u)].sum;
	}

	if (tr[rs(u)].max == tr[u].max) {
		tr[u].sum += tr[rs(u)].sum;
	}
}

void modify(int & u, int l, int r, int p, int add) {
	if (u == 0) {
		u = ++ tot;
	}

	if (l == r) {
		tr[u].max += add;
		tr[u].sum = l;
		return;
	}

	int mid = l + r >> 1;
	if (p <= mid) {
		modify(ls(u), l, mid, p, add);
	} else {
		modify(rs(u), mid + 1, r, p, add);
	}

	pushup(u);
}

void merge(int & u, int v, int l, int r) {
	if (v == 0) {
		return;
	}

	if (u == 0) {
		u = ++ tot;
		tr[u] = tr[v];
		return;
	}

	if (l == r) {
		tr[u].max += tr[v].max;
		return;
	}

	int mid = l + r >> 1;
	merge(ls(u), ls(v), l, mid);
	merge(rs(u), rs(v), mid + 1, r);
	pushup(u);
}

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> c(n + 1);
    for (int i = 1; i <= n; ++ i) {
    	std::cin >> c[i];
    }

    std::vector<std::vector<int>> adj(n + 1);
    for (int i = 1; i < n; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    }

    std::vector<int> root(n + 1);
    std::vector<i64> ans(n + 1);
    auto dfs = [&](auto self, int u, int fa) -> void {
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		self(self, v, u);
    		merge(root[u], root[v], 1, n);
    	}

    	modify(root[u], 1, n, c[u], 1);
    	ans[u] = tr[root[u]].sum;
    };

    dfs(dfs, 1, 0);
    for (int i = 1; i <= n; ++ i) {
    	std::cout << ans[i] << " \n"[i == n];
    }
}

解法二:
之前没学过dsu on tree,果然打edu能学到东西。
dsu on tree可以用于解决不带修的树上询问问题。
dsu on tree,叫做树上启发式合并。每个点对儿子分轻边和重边,对于树上的一个点,与其相连的边中,连向的节点子树大小最大的边叫做重边,其他的边叫轻边。先用一次dfs求出重边,然后第二次dfs每次先跑完所有轻边,并且清除轻边对答案的影响。然后单独跑重边,不清除影响,再跑一遍所有轻边,也先不清除影响,就得到了这个点的答案。最后如果该点到根节点的每个点都重边过来的,就不需要清除该点的影响,否则整棵子树的影响都要清空,因为是先跑轻边,所以轻边要每次清除避免对兄弟节点造成影响,如果是它到根节点的每个节点都是重边,说明它的所有祖先的轻边都已经跑完了,因为每个点都最后一个跑重边,所以这时不会对兄弟节点有影响。
关于时间复杂度,是O(nlogn)的,每个点会被到根节点路径上的每条轻边进行一次dfs,但每个点到根节点路径上只有不超过logn个轻边,因为每个轻边都代表有兄弟节点的子树比它大,那么这棵轻边所连接的子树大小最多是父亲节点这棵子树大小的一半,于是每经过一条轻边就小一半,最多logn次。每个点也最多被重边dfs一次。

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

    std::vector<std::vector<int>> adj(n);
    for (int i = 1; i < n; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    }

    std::vector<int> size(n), son(n, -1);
    auto dfs = [&](auto self, int u, int fa) -> void {
    	size[u] = 1;
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		self(self, v, u);
    		size[u] += size[v];
    		if (son[u] == -1 || size[v] > size[son[u]]) {
    			son[u] = v;
    		}
    	}
    };

    std::vector<int> cnt(n + 1);
    i64 sum = 0, max = 0;
    int Son = -1;
    auto add = [&](auto self, int u, int fa, int val) -> void {
    	cnt[c[u]] += val;
    	if (cnt[c[u]] > max) {
    		max = cnt[c[u]];
    		sum = c[u];
    	} else if (cnt[c[u]] == max) {
    		sum += c[u];
    	}

    	for (auto & v : adj[u]) {
    		if (v == fa || v == Son) {
    			continue;
    		}

    		self(self, v, u, val);
    	}
    };

    std::vector<i64> ans(n);
    auto dfs1 = [&](auto self, int u, int fa, int flag) -> void {
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		if (v != son[u]) {
    			self(self, v, u, 0);
    		}
    	}

    	if (son[u] != -1) {
    		self(self, son[u], u, 1);
    	}

    	Son = son[u];
    	add(add, u, fa, 1);
    	Son = -1;
    	ans[u] = sum;
    	if (!flag) {
    		add(add, u, fa, -1);
    		sum = 0, max = 0;
    	}
    };

    dfs(dfs, 0, -1);
    dfs1(dfs1, 0, -1, 1);
    for (int i = 0; i < n; ++ i) {
    	std::cout << ans[i] << " \n"[i == n - 1];
    }
}

F. Edge coloring of bipartite graph

题意:对二分图的边进行染色,使得没有相邻边颜色相同并且要求颜色最少,求方案。

看起来就很典的题,但我以前却没遇到过。
结论是最少颜色数等于点的最大度数。
首先显然答案大于等于最大度数,因为一个点相连的边都互相相邻。
那么我们怎么证明可以等于呢,参考别人的题解是构造法,并且我们可以按照这个方法求方案。
我们按顺序给边染色,现在考虑对(u,v)染色:设mexu,mexv分别为u,v相连的边已经染的色的mex
如果mexu==mexv,说明两个点对[1,mex1]都恰好染色了,那么我们可以给这条边染色为mexu
否则我们发现不管对(u,v)染色为mexu还是mexv都会有冲突,不妨设染(u,v)mexu,那么发现冲突,我们只需要按照类似找增广路的方式,每次交换和v相连的连mexu,mexv颜色的点染的颜色。

点击查看代码
void solve() {
    int a, b, m;
    std::cin >> a >> b >> m;
    int n = a + b;
    std::vector<std::pair<int, int>> edges(m);
    std::vector<int> in(n);
    for (int i = 0; i < m; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	v += a;
    	-- u, -- v;
    	++ in[u]; ++ in[v];
    	edges[i] = {u, v};
    }

    std::vector f(n, std::vector<int>(n, -1));
    for (int i = 0; i < m; ++ i) {
    	auto & [u, v] = edges[i];
    	int c1 = 0, c2 = 0;
    	while (f[u][c1] != -1) {
    		++ c1;
    	}

    	while (f[v][c2] != -1) {
    		++ c2;
    	}

		f[u][c1] = v;
		f[v][c2] = u;
    	if (c1 == c2) {
    		continue;
    	}

    	for (int i = c2, k = v; ~k; k = f[k][i], i = i ^ c1 ^ c2) {
    		std::swap(f[k][c1], f[k][c2]);
    	}
    }

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

    std::cout << ans << "\n";
    for (int i = 0; i < m; ++ i) {
    	auto & [u, v] = edges[i];
    	for (int j = 0; j < n; ++ j) {
    		if (f[u][j] == v) {
    			std::cout << j + 1 << " \n"[i == m - 1];
    			break;
    		}
    	}
    }
}
posted @   maburb  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示