2024/9/22_&_9/28 CSP-S daimayuan模拟赛复盘
2024/9/22_&_9/28 daimayuan
Day 5 A. 距离
题面描述
你有一个图,其中有 \(n\) 个点和 \(m\) 条有向边,并且每个点有个权值 \(a_1,a_2,\dots,a_n\)。
除了这 \(m\) 条有向边外,如果对于图中两个点 \(u,v\),有 \(a_u \& a_v = a_v\) (即二进制下的与运算),那么会有一条额外的从 \(u\) 到 \(v\) 的有向边。
现在求 \(1\) 号点,到其他所有点的最短距离,也就是经过的最少的有向边数。
输入 & 输出 & 样例 & 数据范围
输入第一行,两个整数 \(n,m\)。
接下来一行一共 \(n\) 个数 \(a_1,a_2,\dots,a_n\)。
接下来 \(m\) 行,每行两个数 \(u,v\),表示一条有向边。
输出一共 \(n\) 行,其中第 \(i\) 行表示到 \(i\) 号点的距离。如果无法到达,那么输出 \(-1\)。
对于所有数据,保证 \(1 \leq n \leq 2\times10^5,0 \leq m \leq 3 \times 10^5,0 \leq a_i < 220\)。
5 2
5 4 2 3 7
1 4
2 3
0
1
2
1
-1
思路解析
首先我们把加边的条件转换一下,即 \(a_u \& a_v = a_v\) 等价于:在集合意义下 \(a_v\) 是 \(a_u\) 的子集。这样我们就可以用子集连边的方法,即不需要对 \(x\) 子集内的每个值连边,只需要分别考虑 \(x\) 的每一个元素将其删去后的 \(x^`\),将 \(x\) 向 \(x^`\) 连边即可,这样可以保证集合 \(x\) 能与它的每一个子集 \(y\) 都直接或间接相连。注意因为间接相连也能传递信息,所以该图上所有边权都为 \(0\)。
我们用上面加边的方法给值域内的所有值都连好边后,此时我们就有了两张图,一张是题目给的原图,另外一张就是我们在值域上建的这张图,我们考虑如何把两张图联系起来。我们考虑对于原图的每一个 \(u\),都将 \(u\) 向值域图上的 \(a_u\) 连边,边权为 \(1\);同时将值域图上的 \(a_u\) 向 \(u\) 连边,边权为 \(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\)。
接下来 \(n−1\) 行,每行两个数,表示一条树上的边。
输出对于每组测试数据,输出一个数,表示最大值。
对于所有数据,保证 \(T \leq 10,2 \leq n \leq 10^5,1 \leq K \leq n\)。
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。记 \(f_i\) 表示若只考虑问题在 \(i\) 的子树中,链的一端为 \(i\),这个子问题的答案是多少。考虑转移,由于链的另一端在子树中,所以肯定是由 \(i\) 的子节点转移给 \(i\),于是遍历每一个子节点求从该子节点转移取最大值即可。
考虑如何统计答案。由于最终的答案链有可能是经过 \(i\) 但不是以 \(i\) 为端点,也就是两个端点 \(u,v\) 的 \(lca(u,v)\) 为 \(i\)。这种情况下就是相当于有两条链 \(u,i\) 和 \(v,i\) 加起来的贡献;而如果确定 \(i\),那么 \((u,i)\) 和 \((v,i)\) 就是 \(i\) 的所有子节点中的最大贡献和次大贡献,此时只要在转移 \(f\) 求最大值时同时求次大值即可。注意因为统计 \(f_i\) 和答案时会删除 \(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;
}

浙公网安备 33010602011771号