题目
题意概要
对于
n
n
n 个点
m
(
2
∣
m
)
m(2|m)
m(2∣m) 条边的简单无向图,如果有至少
⌈
m
2
⌉
\lceil\frac{m}{2}\rceil
⌈2m⌉ 条边是桥,则这个图是 “绝妙的” 。
q ( q ≤ 1 0 5 ) q(q\le 10^5) q(q≤105) 次询问,求出 x ( x ≤ 2 × 1 0 9 ) x(x\le 2\times 10^9) x(x≤2×109) 个点的 “绝妙的” 图中,最多有多少条边。
思路
显然桥不会出现在环上。显然无向图无环则为树。环可以缩点,剩下的连成树。树边即是桥。
设其余的环均已缩点,点数为 n n n 。剩下两个环,大小分别为 a a a 和 b b b ,则可随意选择的边数量为 a ( a − 1 ) + b ( b − 1 ) 2 = a 2 + b 2 − a − b 2 \frac{a(a-1)+b(b-1)}{2}=\frac{a^2+b^2-a-b}{2} 2a(a−1)+b(b−1)=2a2+b2−a−b (无非是将环连成完全图),树边的数量为 n − a − b + 1 n-a-b+1 n−a−b+1 。但合在一起形成大小为 a + b − 1 a+b-1 a+b−1 的环,则可选的边数量为 ( a + b − 1 ) ( a + b − 2 ) 2 = a 2 + b 2 + 2 a b − 3 a − 3 b 2 \frac{(a+b-1)(a+b-2)}{2}=\frac{a^2+b^2+2ab-3a-3b}{2} 2(a+b−1)(a+b−2)=2a2+b2+2ab−3a−3b ,树边的数量仍然为 n − a − b + 1 n-a-b+1 n−a−b+1 。
注意到环的大小至少为 3 3 3 ,即 a , b ≥ 3 a,b\ge 3 a,b≥3 ,就有 a b ≥ a + b ab\ge a+b ab≥a+b (简要证明:不妨设 a ≥ b a\ge b a≥b ,移项,等价于 a ( b − 1 ) ≥ b a(b-1)\ge b a(b−1)≥b ,显然成立)。于是
a 2 + b 2 + 2 a b − 3 a − 3 b 2 ≥ a 2 + b 2 − a − b 2 \frac{a^2+b^2+2ab-3a-3b}{2}\ge\frac{a^2+b^2-a-b}{2} 2a2+b2+2ab−3a−3b≥2a2+b2−a−b
故:两个环合并,一定不劣。所以最终只有一个环。
枚举这个唯一的环的大小为 x x x ,总的边数不能超过两倍的桥,也不能超过图中的可选边数量,最后
a n s = min ( 1 2 x 2 − 3 2 x + n , 2 n − 2 x ) ans=\min\left(\frac{1}{2}x^2-\frac{3}{2}x+n,2n-2x\right) ans=min(21x2−23x+n,2n−2x)
这个式子是化简后的结果,左边原始为 x ( x − 1 ) 2 + n − x \frac{x(x-1)}{2}+n-x 2x(x−1)+n−x 。观察右式,显然 1 2 x 2 − 3 2 x + n \frac{1}{2}x^2-\frac{3}{2}x+n 21x2−23x+n 在 [ 2 , + ∞ ) [2,+\infty) [2,+∞) 上单增,而 2 n − 2 x 2n-2x 2n−2x 单减,显然可以二分求二者的交点。
什么?你问我 [ 2 , + ∞ ) [2,+\infty) [2,+∞) 以外的部分?环最少三个点,兄弟!可以把无环即 a n s = n − 1 ans=n-1 ans=n−1 赋为初值。
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
template < typename T >
void getMax(T&a,const T&b){if(a<b)a=b;}
template < typename T >
void getMin(T&a,const T&b){if(b<a)a=b;}
int main(){
for(int Q=readint(); Q--; ){
int n = readint(), i;
long long ans = n-1;
int L = 3, R = n-1;
while(L+1 < R){
i = (L+R)>>1;
if(i*(i-1ll)/2+(n-i) <= 2ll*(n-i))
L = i;
else R = i;
}
for(i=L; i<=R; ++i)
getMax(ans,min(i*(i-1ll)/2+(n-i),2ll*(n-i)));
printf("%lld\n",ans);
}
return 0;
}