比赛链接:
https://ac.nowcoder.com/acm/contest/35146
B.Capital Program
题意:
\(n\) 座城市,\(n - 1\) 条道路,即一个带权树,选定其中的 \(k\) 个点作为这些城市的首都,求出非首都城市到首都城市的最大距离的最小值。
思路:
容易想到,\(k\) 个城市就是距离树的中心最近的 \(k\) 个点(包括中心)。
所以,先求树的中心。通过树的中心向外扩张,求出最大距离。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e5 + 10;
LL n, k, d[N], c, pa[N], md[N], a[N], ans;
vector <LL> g[N];
void dfs(LL u, LL fa){
for (auto v : g[u]){
if (v == fa) continue;
d[v] = d[u] + 1;
if (d[v] > d[c]) c = v;
dfs(v, u);
}
}
void findC(LL u, LL fa){
for (auto v : g[u]){
if (v == fa) continue;
d[v] = d[u] + 1;
pa[v] = u;
if (d[v] > d[c]) c = v;
findC(v, u);
}
}
void calD(LL u, LL fa){
md[u] = d[u];
for (auto v : g[u]){
if (v == fa) continue;
d[v] = d[u] + 1;
calD(v, u);
md[u] = max(md[u], md[v]);
}
}
int main(){
cin >> n >> k;
for (int i = 1; i < n; i ++ ){
LL u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
d[c] = 0;
findC(c, 0);
LL t = d[c];
for (int i = 1; i <= (t + 1) / 2; i ++ )
c = pa[c];
d[c] = 0;
calD(c, 0);
for (int i = 1; i <= n; i ++ )
a[i] = md[i] - d[i] + 1;
sort(a + 1, a + n + 1, [](LL a, LL b){
return a > b;
});
for (int i = k + 1; i <= n; i ++ )
ans = max(ans, a[i]);
cout << ans << "\n";
return 0;
}
也可以将叶子节点不断地剪去,直到剩下 \(k\) 个节点,求解最大距离。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e5 + 10;
LL n, k, deg[N], cnt, d[N], ans;
vector <LL> g[N];
void bfs(){
queue <LL> q;
for (int i = 1; i <= n; i ++ )
if (deg[i] == 1){
d[i] = 1;
q.push(i);
}
while (q.size()){
LL u = q.front();
q.pop();
cnt ++ ;
if (cnt + k > n) break;
ans = max(ans, d[u]);
for (auto v : g[u]){
d[v] = max(d[u] + 1, d[v]);
if ( -- deg[v] == 1){
q.push(v);
}
}
}
cout << ans << "\n";
}
int main(){
cin >> n >> k;
for (int i = 0; i < n - 1; i ++ ){
LL u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
deg[u] ++ ;
deg[v] ++ ;
}
bfs();
return 0;
}
E.Plus
题意:
给定一个 \(n\),找出满足:
1.p 和 q 都是质数
2.1 <= p <= q <= n
3.p ^ q + q ^ p 是质数
的整数对\((p, q)\)的数量。
思路:
只有 (2, 3) 这一对,其它都是不行的。
不会证明。。。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL n;
int main(){
cin >> n;
if (n <= 2) cout << "0\n";
else cout << "1\n2 3\n";
return 0;
}
I.Generator
题意:
有一个随机均匀生成器。现在有一个 \(n\),按下按钮后,它会随机变成 \([1, n]\) 中任意一个数,即所有数的出现都是等概率的。求 \(n\) 按下多少次按钮后变成 1 的期望。
思路
定义 \(f[i]\) 为 \(i\) 变成 1 的期望次数。
\(f[1] = 1\)
\(f[2] = \frac{1}{2} + \frac{1}{2} (f[2] + 1) = 2\) (\(\frac{1}{2}\) 的概率变成 1,\(\frac{1}{2} 的概率还是 2,但是变成 2 之后再去转换成 1,就意味着多了一步\))
\(f[3] = \frac{1}{3} + \frac{1}{3} (f[2] + 1) + \frac{1}{3} (f[3] + 1) = 2.5\)
.
.
.
\(f[n] = \frac{1}{n} + \frac{1}{n} (f[2] + 1) + ... + \frac{1}{n} (f[n] + 1)\)
求解 \(f[n]\)。
两边都乘上 \(n\)
\(n * f[n] = 1 + f[2] + 1 + ... + f[n] + 1\)
\(n * f[n] = n - 1 + f[1] + f[2] + ... + f[n]\)
定义 \(s_n = f[1] + f[2] + ... + f[n]\)
\(n * f[n] = n - 1 + s_n\)---1式
\((n - 1) * f[n - 1] = n - 2 + s_{n - 1}\)---2式
1式 - 2式得到
\(n * f[n] - (n - 1) * f[n - 1] = 1 + f[n]\)
\(f[n] - f[n - 1] = \frac{1}{n - 1}\)
所以答案从第二项开始就是调和级数的和 + 1。
可以预处理 1e6 以内的调和级数,然后大于 1e6 的通过欧拉常数近似替代求。
调和级数求和的推导链接:https://blog.csdn.net/qq_44009311/article/details/99947219
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n;
double e = 0.5772156649;
double f[N];
int main(){
cin >> n;
f[1] = 1;
for (int i = 2; i <= 1e6; i ++ )
f[i] = f[i - 1] + 1.0 / i;
if (n <= 1e6) printf("%.12lf\n", f[n - 1] + 1);
else printf("%.12lf\n", log(n - 1) + e + 1);
return 0;
}
K.Maze
题意:
一个 \(n * n\) 的迷宫,'.' 表示可以通过,'*' 表示障碍,不能向一个方向连续走 \(m\) 步以上,问从(1, 1)出发到达 (n, n) 最少的步数是多少。
思路:
容易想到 \(bfs\)。
因为沿着同一个方向走 \(m\) 步以上,所以队列中的节点要包含,坐标、方向、沿着这个方向移动的步数、以及移动的总步数。
4 2
.***
.***
.***
....
这一组数据的答案为 10。要回头走一步,然后再向前走。
考虑这一组数据可以发现,对于每一个位置不同方向不同步数的方案都要记录下来,即 \(bfs\) 的标记走过的数组要开四维。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 110;
LL T, n, m, ans[N][N][4][N];
LL dx[] = {-1, 0, 1, 0}, dy[] = {0, -1, 0, 1};
char a[N][N];
struct node{
LL x, y, d, dt, st;
};
void bfs(){
queue <node> q;
for (int i = 0; i < 4; i ++ )
q.push({1, 1, i, 0, 0});
while (q.size()){
node t = q.front();
q.pop();
for (int i = 0; i < 4; i ++ ){
LL nx = t.x + dx[i], ny = t.y + dy[i];
if (nx < 1 || ny < 1 || nx > n || ny > n || a[nx][ny] == '*') continue;
LL nt = t.dt + 1; //按照当前方向移动的步数
if (i != t.d) nt = 1;
if (nt > m || ans[nx][ny][i][nt]) continue;
if (nx == n && ny == n){
cout << t.st + 1 << "\n";
return;
}
ans[nx][ny][i][nt] = t.st + 1;
q.push({nx, ny, i, nt, t.st + 1});
}
}
cout << "-1\n";
}
void Clear(){
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
for (int k = 0; k < 4; k ++ )
for (int t = 1; t <= n; t ++ )
ans[i][j][k][t] = 0;
}
void solve(){
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
cin >> a[i][j];
bfs();
Clear();
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T -- )
solve();
return 0;
}
L.Polygon
题意:
给定 \(n\) 条边长度,判断能否组成 \(n\) 边形。
思路:
判断最大的那条边是不是比其它所有边之和小就行。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL n, x, mx, s;
int main(){
cin >> n;
for (int i = 0; i < n; i ++ ){
cin >> x;
s += x;
mx = max(mx, x);
}
cout << (s - mx > mx ? "YES\n" : "NO\n");
return 0;
}