Description:
1<=n<=10^5
题解:
首先对于这种树上路径问题是很容易想到点分治的,细节比较复杂,也许需要一个set,常数爆炸。
比较联赛的做法是并查集。
先将点按权值从大到小排序。
依次枚举每个点,将它和与它相邻且权值大于它的点合并成一棵树。
每次合并,新树的直径的端点一定是原来两棵树中的两条直径的四个端点中的两个,这个易证。
直径上的最小值不一定是当前的点,但是这没有关系,因为它一定在前面就被计算过了。所以ans=max(ans,直径长度×当前点权)
Code:
#include<cstdio>
#include<algorithm>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;
const int mo = 1 << 30;
const int N = 4000;
int T, n, m, p[N], mu[N + 5];
bool bz[N + 5];
ll ans;
void Build() {
mu[1] = 1;
fo(i, 2, N) {
if(!bz[i]) p[++ p[0]] = i, mu[i] = -1;
fo(j, 1, p[0]) {
int k = i * p[j];
if(k > N) break;
bz[k] = 1;
if(i % p[j] == 0) {
mu[k] = 0; break;
}
mu[k] = -mu[i];
}
}
}
int main() {
Build();
for(scanf("%d", &T); T; T --) {
scanf("%d %d", &n, &m);
if(n > m) swap(n, m);
n --; m --;
ans = 0;
fo(i, 1, n) if(mu[i] != 0) {
int sf, sg;
int c = n / i, c2 = (n + 1) / 2 / i, s;
sf = (1 + c2) * c2 / 2 * i;
sf += (n + 1) * (c - c2) - (c2 + 1 + c) * (c - c2) / 2 * i;
c = m / i, c2 = (m + 1) / 2 / i, s;
sg = (1 + c2) * c2 / 2 * i;
sg += (m + 1) * (c - c2) - (c2 + 1 + c) * (c - c2) / 2 * i;
int s1 = (n + 1) * (n / i) - (n / i) * (n / i + 1) * i / 2;
int s2 = (m + 1) * (m / i) - (m / i) * (m / i + 1) * i / 2;
ans += mu[i] * (sf * s2 + sg * s1 - sf * sg);
}
ans *= 2; ans += n + m + 2;
ans = (ans % mo + mo) % mo;
printf("%lld\n", ans);
}
}