2024/9/22_&_9/28 CSP-S daimayuan模拟赛复盘

2024/9/22_&_9/28 daimayuan

contest link for Day 5(9/22)

contest link for Day 6(9/28)

Day 5 A. 距离

题面描述

你有一个图,其中有 n 个点和 m 条有向边,并且每个点有个权值 a1,a2,,an

除了这 m 条有向边外,如果对于图中两个点 u,v,有 au&av=av (即二进制下的与运算),那么会有一条额外的从 uv 的有向边。

现在求 1 号点,到其他所有点的最短距离,也就是经过的最少的有向边数。

输入 & 输出 & 样例 & 数据范围

输入第一行,两个整数 n,m

接下来一行一共 n 个数 a1,a2,,an

接下来 m 行,每行两个数 u,v,表示一条有向边。

输出一共 n 行,其中第 i 行表示到 i 号点的距离。如果无法到达,那么输出 1

对于所有数据,保证 1n2×105,0m3×1050ai<220

5 2
5 4 2 3 7
1 4
2 3

0
1
2
1
-1

思路解析

首先我们把加边的条件转换一下,即 au&av=av 等价于:在集合意义下 avau 的子集。这样我们就可以用子集连边的方法,即不需要对 x 子集内的每个值连边,只需要分别考虑 x 的每一个元素将其删去后的 x,将 xx 连边即可,这样可以保证集合 x 能与它的每一个子集 y 都直接或间接相连。注意因为间接相连也能传递信息,所以该图上所有边权都为 0

我们用上面加边的方法给值域内的所有值都连好边后,此时我们就有了两张图,一张是题目给的原图,另外一张就是我们在值域上建的这张图,我们考虑如何把两张图联系起来。我们考虑对于原图的每一个 u,都将 u 向值域图上的 au 连边,边权为 1;同时将值域图上的 auu 连边,边权为 0

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1.3e6 + 10;
int n, m, a[N], dis[N];
vector<int> g1[N], g2[N];
int main() {
	cin >> n >> m;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		g1[i].push_back(n + a[i] + 1); g2[n + a[i] + 1].push_back(i);
	}
	for(int i = 1, u, v; i <= m; i++) {
		cin >> u >> v;
		g1[u].push_back(v);
	}
	for(int i = 0; i < (1 << 20); i++) {
		for(int j = 0; j < 20; j++) {
			if((i >> j) & 1) {
				g2[n + i + 1].push_back(n + i + 1 - (1 << j));
			}
		}
	}
	memset(dis, 0x3f, sizeof(dis)); dis[1] = 0;
	deque<int> q; q.push_back(1);
	while(!q.empty()) {
		int u = q.front(); q.pop_front();
		for(auto v : g1[u]) {
			if(dis[v] > dis[u] + 1) {
				dis[v] = dis[u] + 1;
				q.push_back(v);
			}
		}
		for(auto v : g2[u]) {
			if(dis[v] > dis[u]) {
				dis[v] = dis[u];
				q.push_front(v);
			}
		}
	}
	for(int i = 1; i <= n; i++) {
		if(dis[i] < 0x3f3f3f3f) cout << dis[i] << '\n';
		else puts("-1");
	}
	return 0;
}

Day 6 A. 伐木

题面描述

你有一棵 n 个点的树。你可以选择两个不同的点 (x,y),把这两个点的简单路径上的点 ( 包括 x,y) 删除。对于一条边(u,v),如果其中任意一个端点在简单路径上,这条边也会被删除。

整棵树会变成若干个连通块。你希望最大化点数大于等于 K 的连通块个数。

输入 & 输出 & 样例 & 数据范围

输入多组测试数据,第一行一个整数 T 表示数据组数。对于每组测试数据:

第一行两个整数 n,K

接下来 n1 行,每行两个数,表示一条树上的边。

输出对于每组测试数据,输出一个数,表示最大值。

对于所有数据,保证 T10,2n105,1Kn

2
7 3
1 2
2 3
3 4
4 5
5 6
6 7
9 3
1 2
1 3
1 4
4 5
4 6
4 7
7 8
7 9

1
2

思路解析

问题在树上,是求最大贡献的问题,考虑树形 dp。记 fi 表示若只考虑问题在 i 的子树中,链的一端为 i,这个子问题的答案是多少。考虑转移,由于链的另一端在子树中,所以肯定是由 i 的子节点转移给 i,于是遍历每一个子节点求从该子节点转移取最大值即可。

考虑如何统计答案。由于最终的答案链有可能是经过 i 但不是以 i 为端点,也就是两个端点 u,vlca(u,v)i。这种情况下就是相当于有两条链 u,iv,i 加起来的贡献;而如果确定 i,那么 (u,i)(v,i) 就是 i 的所有子节点中的最大贡献和次大贡献,此时只要在转移 f 求最大值时同时求次大值即可。注意因为统计 fi 和答案时会删除 i,所以需要单独判断一下 i 的子树大小减去 i 本身的贡献。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int T, n, k, ans = 0, sz[N], f[N];
vector<int> g[N];
void dfs(int u, int fa) {
	sz[u] = 1;
	for(auto v : g[u]) {
		if(v == fa) continue;
		dfs(v, u);
		sz[u] += sz[v];
	}
	int cnt = 0, mx1 = -2e9, mx2 = -2e9;
	for(auto v : g[u]) {
		if(v == fa) continue;
		cnt += (sz[v] >= k);
		if(f[v] > mx1) mx2 = mx1, mx1 = f[v];
		else if(f[v] > mx2) mx2 = f[v];
	}
	f[u] = cnt + max(0, mx1) - (sz[u] >= k);
	ans = max(ans, cnt + mx1 + max(0, mx2) + ((n - sz[u]) >= k));
}
int main() {
	cin >> T;
	while(T--) {
		cin >> n >> k;
		
		memset(f, 0, (n + 5) * sizeof(int)); ans = 0;
		for(int i = 1; i <= n; i++) g[i].clear();
		
		for(int i = 1, u, v; i <= n - 1; i++) {
			cin >> u >> v;
			g[u].push_back(v); g[v].push_back(u);
		}
		
		dfs(1, 0);
		cout << ans << '\n';
	}
	return 0;
}
posted @   2020luke  阅读(63)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示