#P1690. 2024.3.10-第三题-米小游的神木
-
1000ms
Tried: 151
Accepted: 38
Difficulty: 10
所属公司 :
米哈游
时间 :2024年3月10日
-
算法标签>DFS
2024.3.10-第三题-米小游的神木
思路:计算贡献法
这个题本质上考的就是大家的计算思维。这个通常在ACM/OI中有考察。而在部分好一点的大学中,会开一门课《具体数学》,在学《求和式》一章时,里面也会有涉及到这种思想。
朴素想法
一个朴素的想法是:对每个节点求f(i) :对于每个i , 我们统计出i的子树集合。接下来枚举每个子孙节点,判断是否j是i的因子,是的话f(i)+=1
写成数学式是:
其中[表达式]是艾弗森符号,即逻辑表达式。命题成立则为1,否则为0
交换贡献/交换求和式
很容易发现以上方法复杂度是O(n2)的。我们考虑交换求和顺序,即考虑每个点i作为子孙节点,那么他所有的祖先节点j , 如果满足以上关系即答案加一。
具体来讲,我们在做dfs的过程中,维护一个从根到当前节点的set(祖先节点集合)。对每个节点,我们考虑在set力寻找他的倍数i,2i,3i,... 。
写成数学式就是:

set的查找是O(1)的 , 根据欧拉定理,∑i∑j=i,2i,3i,...=O(nlogn)
至此,我们使用了简单的交换贡献法(从数学上来说,就是交换求和式(见《具体数学》第二章 和式 2.5 一般性方法))。
python
n , x = map(int, input().split()) # 读入
e = [[] for i in range(n + 1)]
for i in range(n - 1): # 建树
u , v = map(int, input().split())
e[u].append(v)
e[v].append(u)
s = set() # 维护father这个集合
ans = 0
def dfs(u, p):
global ans
s.add(u) # 加入father
for i in range(u , n + 1 , u): # 枚举i的倍数
if i in s: # 如果i在father中
ans += 1
for v in e[u]: # 遍历u的子节点
if v != p:
dfs(v, u)
s.remove(u)
return
dfs(x, 0) # 从x开始dfs
print(ans) # 输出答案
c++
#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10;
vector<int>g[N];
unordered_set<int> s;
long long ans = 0;
void dfs(int u, int p, int n) {
s.insert(u);
for (int i = u; i <= n; i += u) {
if (s.find(i) != s.end()) {
ans += 1;
}
}
for (int v : g[u]) {
if (v != p) {
dfs(v, u, n);
}
}
s.erase(u);
}
int main() {
int n, x;
cin >> n >> x;
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(x, 0, n);
cout << ans << endl;
return 0;
}
java
import java.util.*;
class Main{
static int N = (int)1e5+10;
static List<Integer>[] g = new ArrayList[N];
static int[] f = new int[N];
static boolean[] st = new boolean[N];
static int n;
static int ans;
static Set<Integer> set = new HashSet<>();
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
int x = scan.nextInt();
Arrays.setAll(g, e->new ArrayList<>());
for (int i=0; i<n-1; i++){
int a = scan.nextInt();
int b = scan.nextInt();
g[a].add(b);
g[b].add(a);
}
dfs(x);
System.out.println(ans);
scan.close();
}
public static void dfs(int u){
st[u]=true;
set.add(u);
// for (int i=1; i<=n; i++){
// if (st[i] && i%u==0) ans++;
// }
// for (int i:set){
// if (st[i] && i%u==0) ans++;
// }
for (int i=u; i<=n; i+=u){
if (set.contains(i)) ans++;
}
for (int x:g[u]){
if (!st[x]) dfs(x);
}
set.remove(u);
// st[u] = false;
}
}
题目描述
仙舟罗浮上有一棵建木,据说服下建木生成的神实就可以得到“无尽形寿“的身体,蜕变为长生种。米小游是短生种,因此她很想找到神实。 建木是一棵有n个节点的有根树,节点编号为1~n,根节点为x
对于编号为i的节点,f(i)表示以i为根的子树中,节点编号是i的因子的节点个数。
建木上神实的总数就是∑inf(i),米小游想知道建木上神实的总数是多少。
输入描述
第一行包含两个整数n,x(1≤x,n≤105),表示树的节点个数,根节点编号
接下来n−1行,每行两个数u,v(1≤u,v≤n),表示一条u到v的树边。数据保证一定是一棵树。
输出描述
输出包含一个整数,表示建木上神实的总数。
样例
输入
4 4
1 2
4 3
2 4
输出
7
说明
以节点4为根的子树的节点有[1,2,3,4],其中[1,2,4]是4的因子,f(4)=3.
以节点2为根的子树的节点有[1,2],其中[1,2]是2的因子,f(2)= 2。
以节点1为根的子树的节点有[1],其中|1]是1的因子,f(1)= 1。
以节点3为根的子树的节点有[3],其中[3]是3的因子,f(3)= 1。
总和为1+2+1+3=7