CF1179D Fedor Runs for President
题意
-
给一棵
n
个点的树,求加入一条边后,最多有多少条无向简单路径 -
简单路径定义为任意一个点最多经过一次的路径
思路
- 我们若将
i
和j
相连,我们可以将i
到j
的路径拉直- 路径上每个点有一个以该点为根的子树,记子树大小为
size[i]
- 该路径上不同子树之间的点相互访问的简单路径增加了一条
- 增加路径数
∑
(
n
−
s
i
z
e
[
i
]
)
∗
s
i
z
e
[
i
]
=
1
2
(
n
2
−
∑
s
i
z
e
[
i
]
2
)
\sum {(n-size[i])*size[i]} = \frac{1}{2} (n^2- \sum size[i]^2)
∑(n−size[i])∗size[i]=21(n2−∑size[i]2),
i
为路径上的点 - 综上,我们要求的就是 ∑ s i z e [ i ] \sum size[i] ∑size[i]的最小值
- 路径上每个点有一个以该点为根的子树,记子树大小为
- 然后我们发现一个规律:路径的两端必定为叶子或者根
- 若不为叶子,我可以让他的端点向子树方向扩展,那么得到
size[i]
更小( n 2 > x 2 + ( n − x ) 2 n^2>x^2+(n-x)^2 n2>x2+(n−x)2)
- 若不为叶子,我可以让他的端点向子树方向扩展,那么得到
- 令
dp[i]
为以i
为根的子树中 ∑ s i z e [ i ] \sum size[i] ∑size[i]最小的链- 转移方程为 d p [ i ] = m i n ( d p [ j ] + ( s i z e [ i ] − s i z e [ j ] ) 2 ) dp[i]=min(dp[j]+(size[i]-size[j])^2) dp[i]=min(dp[j]+(size[i]−size[j])2)
n_size[now] = 1;
for (auto it : E[now])
{
if (it == fa)
continue;
dfs(it, now);
n_size[now] += n_size[it];
}
dp[now] = n_size[now] * n_size[now];
for (auto it : E[now])
{
if (it == fa)
continue;
dp[now] = min(dp[now], dp[it] + (n_size[now] - n_size[it]) * (n_size[now] - n_size[it]));
}
- 对于根
s
,可以将i
,j
和并- a n s = m i n ( a n s , d p [ i ] + d p [ j ] + ( n − s i z e [ i ] − s i z e [ j ] ) 2 ) ans=min(ans,dp[i]+dp[j]+(n-size[i]-size[j])^2) ans=min(ans,dp[i]+dp[j]+(n−size[i]−size[j])2)
- 可以将
s
与i
合并, a n s = m i n ( d p [ i ] + ( n − s i z e [ i ] ) 2 ) ans=min(dp[i]+(n-size[i])^2) ans=min(dp[i]+(n−size[i])2)
- 对上面
n
2
n^2
n2做法显然会
t
,我们尝试斜率优化- a n s = d p [ i ] + d p [ j ] + ( n − s i z e [ i ] − s i z e [ j ] ) 2 ans = dp[i] + dp[j] + (n - size[i] - size[j]) ^ 2 ans=dp[i]+dp[j]+(n−size[i]−size[j])2
- d p [ j ] + ( n − s i z e [ j ] ) 2 = 2 ∗ s i z e [ i ] ∗ ( n − s i z e [ j ] ) + a n s − s i z e [ i ] 2 − d p [ i ] dp[j] + (n - size[j]) ^ 2 = 2 * size[i] * (n - size[j]) + ans - size[i] ^ 2 - dp[i] dp[j]+(n−size[j])2=2∗size[i]∗(n−size[j])+ans−size[i]2−dp[i]
x: n-size[j]
- y : d p [ j ] + ( n − s i z e [ j ] ) 2 y: dp[j] +(n-size[j])^2 y:dp[j]+(n−size[j])2
k: 2*size[i]
- b : a n s − s i z e [ i ] 2 − d p [ i ] b: ans- size[i] ^2 - dp[i] b:ans−size[i]2−dp[i]
register LL x, y, k, b;
q[top = 1] = make_pair(n - s[1].first, s[1].second + (n - s[1].first) * (n - s[1].first));
for (int i = 2; i <= tot; i++)
{
x = n - s[i].first;
y = s[i].second + x * x;
k = 2 * s[i].first;
int pos = binary_search(k);
b = q[pos].second - k * q[pos].first;
ans = min(ans, b + s[i].first * s[i].first + s[i].second);
//(y2-y1)/(x2-x1) >= (y3-y1)/(x3-x1)
while (top > 1 && (q[top].second - q[top - 1].second) * (x - q[top - 1].first) >= (y - q[top - 1].second) * (q[top].first - q[top - 1].first))
top--;
q[++top] = make_pair(x, y);
}
ans = min(ans, dp[now] + (n - n_size[now]) * (n - n_size[now]));
a[now] = make_pair(n_size[now], dp[now]);
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
typedef long long LL;
LL n;
vector<int> E[maxn];
LL n_size[maxn], dp[maxn];
//dp表示以 i 为根的子数,连接叶子节点 子树平方和最小的路径
typedef pair<LL, LL> pll;
bool cmp(pll& a, pll& b)
{
return a.first > b.first;
}
pll a[maxn], s[maxn], q[maxn];
int top;
LL binary_search(LL k)
{
if (top == 1)
return 1;
int l = 2, r = top, mid, ans = 1;
while (l <= r) {
mid = (l + r) >> 1;
if ((q[mid].second - q[mid - 1].second) <= k * (q[mid].first - q[mid - 1].first))
ans = max(ans, mid),
l = mid + 1;
else
r = mid - 1;
}
return ans;
}
LL ans = 1e18;
void dfs(int now, int fa)
{
n_size[now] = 1;
for (auto it : E[now]) {
if (it == fa)
continue;
dfs(it, now);
n_size[now] += n_size[it];
}
dp[now] = n_size[now] * n_size[now];
for (auto it : E[now]) {
if (it == fa)
continue;
dp[now] = min(dp[now], dp[it] + (n_size[now] - n_size[it]) * (n_size[now] - n_size[it]));
}
// ans = dp[i] + dp[j] + (n - size[i] - size[j]) ^ 2
// dp[j] + (n - size[j]) ^ 2 = 2 * size[i] * (n - size[j]) + ans - size[i] ^ 2 - dp[i]
int tot = 0;
for (auto it : E[now]) {
if (it == fa)
continue;
s[++tot] = a[it];
}
// x: n-size[j]
// y: dp[j] +(n-size[j])^2
// k: 2*size[i]
// b: ans- size[i] ^2 - dp[i]
sort(s + 1, s + 1 + tot, cmp);
int l = 1, r = 1;
register LL x, y, k, b;
q[top = 1] = make_pair(n - s[1].first, s[1].second + (n - s[1].first) * (n - s[1].first));
for (int i = 2; i <= tot; i++) {
x = n - s[i].first;
y = s[i].second + x * x;
k = 2 * s[i].first;
int pos = binary_search(k);
b = q[pos].second - k * q[pos].first;
ans = min(ans, b + s[i].first * s[i].first + s[i].second);
//(y2-y1)/(x2-x1) >= (y3-y1)/(x3-x1)
while (top > 1 && (q[top].second - q[top - 1].second) * (x - q[top - 1].first) >= (y - q[top - 1].second) * (q[top].first - q[top - 1].first))
top--;
q[++top] = make_pair(x, y);
}
ans = min(ans, dp[now] + (n - n_size[now]) * (n - n_size[now]));
a[now] = make_pair(n_size[now], dp[now]);
}
int main()
{
// freopen("out.txt", "r", stdin);
scanf("%I64d", &n);
int u, v;
for (int i = 1; i < n; i++) {
scanf("%d%d", &u, &v);
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1, 0);
//cout << ans << '\n';
printf("%I64d\n", n * (n - 1) / 2 + (n * n - ans) / 2);
return 0;
}