数字转换(树型dp,最远点对)

LOJ10155
题意:对于数字 x x x而言,如果他的约数之和(不包括自身) y y y满足 x > y x>y x>y,那么 x x x可以相互转变 y y y。给定数字 n ( 1 ≤ n ≤ 5 × 1 0 4 ) n(1\le n \le 5\times 10^4) n(1n5×104),问数字最多转换次数为多少。
思路:

  1. 对于数字 1 1 1 n n n,如果满足上述条件则数字间连双向边。由于条件需要保证 x > y x>y x>y,因此无法形成回路。图是一个树的形状。
  2. 由1的分析可以发现要求转变最多次数就是要求最远点对,这里路径权重都是1。
  3. 注意可以是森林
  4. 在连边的处理上,可以选择试除法( n n n\sqrt{n} nn ):对于每个数字 x x x进行约数枚举,并且将约数加到 s u m [ x ] sum[x] sum[x]中。但是也可以采取反向思考,枚举所有和,把他们加到原数 s u m [ x ] sum[x] sum[x]中,这样就变成了一个调和级数,复杂度降为 n l o g ( n ) nlog(n) nlog(n)
  5. 在进行连边的时候不要枚举1,因为 s u m [ 1 ] = 0 sum[1]=0 sum[1]=0,但是转化不包含0,会产生错误。

AC代码:

const int N = 1e5 + 100;
int h[N], e[N], ne[N], idx, w[N];
int n, ans;
int has[N];
int sum[N];
void add(int a, int b, int c) {
    
    
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int dfs(int u, int fa) {
    
    
	int d1 = 0, d2 = 0;
	has[u] = 1;
	for (int i = h[u]; i != -1; i = ne[i]) {
    
    
		int j = e[i];
		if (j == fa)continue;
		int d = dfs(j, u) + 1;
		if (d >= d1) {
    
    
			d2 = d1; d1 = d;
		}
		else if (d > d2)d2 = d;
	}
	ans = max(ans, d1 + d2);
	return d1;
}

int main() {
    
    
	cin >> n;
	memset(h, -1, sizeof(h));
	for (int i = 1; i <= n; i++) {
    
    
		for (int j = 2; j <= n / i; j++) {
    
    
			sum[i*j] += i;
		}
	}
	for (int i = 2; i <= n; i++) {
    
    
		if (i > sum[i]) {
    
    
			add(i, sum[i], 1);
			add(sum[i], i, 1);
		}
	}
	for (int i = 1; i <= n; i++) {
    
    
		if (has[i] == 0) {
    
    
			dfs(i, -1);
		}
	}
	cout << ans << endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44986601/article/details/114683545
今日推荐