P12375 「LAOI-12」MST? 题解
前言:感谢 Billhqh9 与 yjs0929 帮我查的神秘平方求和公式,不感谢 yjs0929 偷偷 JC 我。
首先考虑 Kruskal 的过程,发现每次都是选出当前最小的 且两端点不在同一连通块的边加入生成树。
所以一条"无用"的边就是两端点位于同一连通块内。
然后就尽量贪心地令小的边无用,下一条边就是生成树边。
所以就贪心地用边把图填成完全图后再加下一条边。
举个例子:

在这里,我们先把前三个点弄成完全图,然后再链接 1 1 1 和 4 4 4。
我们定义一个点的前置边为链接这个点的第一条边。
如点 1 1 1 的前置边边权是 1 1 1,点 3 3 3 的前置边边权是 2 2 2,点 4 4 4 的前置边边权是 4 4 4,
然后我们发现,由于 n n n 个点的完全图的边数是 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1),所以第 i i i 个点的前置边的边权是 ( i − 1 ) ( i − 2 ) 2 + 1 \frac{(i-1)(i-2)}{2}+1 2(i−1)(i−2)+1。
然后我们就贪心地不断把当前点搞成完全图后再添加边。
设最大可以把 t t t 个点的图搞成完全图,则 t = max { m − t ( t − 1 ) 2 ≥ n − t } t=\max \{m-\frac{t(t-1)}{2}\ge n-t\} t=max{m−2t(t−1)≥n−t},整理完是 t = max { t 2 − 3 t ≤ 2 ( m − n ) } t=\max \{t^2-3t\le 2(m-n)\} t=max{t2−3t≤2(m−n)}。
这玩意可以用二分 O ( log m ) O(\log m) O(logm) 求出来。
然后设弄成完全图后还剩下 a a a 条边和 b b b 个点。显然 b = n − t b=n-t b=n−t, a ≥ b a\ge b a≥b。
而我们只需要用最大 的 b b b 条边连接 b b b 个点,其他可以瞎连。
此时我们还剩下边集 E = { m − a + 1 , m − a + 2 ... , m } E=\{m-a+1,m-a+2\dots,m\} E={m−a+1,m−a+2...,m},显然 m − a + 1 m-a+1 m−a+1 是一定会被选的,所以还剩下有用的 b − 1 b-1 b−1 条边与 b − 1 b-1 b−1 个点,边权和为 [ m + ( m − b + 2 ) ] ( b − 1 ) 2 = [ 2 m + 2 − b ] ( b − 1 ) 2 \frac{[m+(m-b+2)](b-1)}{2}=\frac{[2m+2-b](b-1)}{2} 2[m+(m−b+2)](b−1)=2[2m+2−b](b−1)。
所以总答案的公式:
a n s = ∑ i = 2 t [ ( i − 1 ) ( i − 2 ) 2 + 1 ] + t ( t − 1 ) 2 + 1 + ( 2 m + 2 − b ) ( b − 1 ) 2 ans=\sum_{i=2}^t[\frac{(i-1)(i-2)}{2}+1]+\frac{t(t-1)}{2}+1+\frac{(2m+2-b)(b-1)}{2} ans=i=2∑t[2(i−1)(i−2)+1]+2t(t−1)+1+2(2m+2−b)(b−1)
然后我做到这里的时候先手算了几个样例,确认没错了才敢继续往下推。感觉十分值得学习。
插入个前置知识(平方求和公式): ∑ i = 1 n i 2 = n ( n + 1 ) ( 2 n + 1 ) 6 \sum_{i=1}^n i^2=\frac{n(n+1)(2n+1)}{6} ∑i=1ni2=6n(n+1)(2n+1)。
设 s = t ( t + 1 ) ( 2 t + 1 ) 6 , B = ( 2 m + 2 − b ) ( b − 1 ) 2 s=\frac{t(t+1)(2t+1)}{6},B=\frac{(2m+2-b)(b-1)}{2} s=6t(t+1)(2t+1),B=2(2m+2−b)(b−1)。
然后就继续化简式子:
a n s = ∑ i = 2 t [ ( i − 1 ) ( i − 2 ) 2 + 1 ] + t ( t − 1 ) 2 + 1 + B = ∑ i = 2 t ( i − 1 ) ( i − 2 ) 2 + ( t − 1 ) + t ( t − 1 ) 2 + 1 + B = ∑ i = 2 t i 2 − 3 ∑ i = 2 t i 2 + t − 1 + t + t ( t − 1 ) 2 + B = s − 1 − 3 t 2 + 3 t 2 + 3 + 2 t − 2 2 + t + t ( t − 1 ) 2 + B = s − 3 t ( t + 1 ) 2 + 2 t 2 + t + t ( t + 1 ) 2 + B ans=\sum_{i=2}^t[\frac{(i-1)(i-2)}{2}+1]+\frac{t(t-1)}{2}+1+B\\ =\sum_{i=2}^t\frac{(i-1)(i-2)}{2}+(t-1)+\frac{t(t-1)}{2}+1+B\\ =\frac{\sum_{i=2}^t i^2-3\sum_{i=2}^t i}{2}+t-1+t+\frac{t(t-1)}{2}+B\\ =\frac{s-1-\frac{3t^2+3t}{2}+3+2t-2}{2}+t+\frac{t(t-1)}{2}+B\\ =\frac{s-\frac{3t(t+1)}{2}+2t}{2}+t+\frac{t(t+1)}{2}+B ans=i=2∑t[2(i−1)(i−2)+1]+2t(t−1)+1+B=i=2∑t2(i−1)(i−2)+(t−1)+2t(t−1)+1+B=2∑i=2ti2−3∑i=2ti+t−1+t+2t(t−1)+B=2s−1−23t2+3t+3+2t−2+t+2t(t−1)+B=2s−23t(t+1)+2t+t+2t(t+1)+B
所以!答案为:
s − 3 t ( t + 1 ) 2 + 2 t 2 + t + t ( t + 1 ) 2 + B \frac{s-\frac{3t(t+1)}{2}+2t}{2}+t+\frac{t(t+1)}{2}+B 2s−23t(t+1)+2t+t+2t(t+1)+B
其中:
t = max { t 2 − 3 t ≤ 2 ( m − n ) } b = n − t s = n ( n + 1 ) ( 2 n + 1 ) 2 B = ( 2 m + 2 − b ) ( b − 1 ) 2 t=\max \{t^2-3t\le 2(m-n)\}\\ b=n-t\\ s=\frac{n(n+1)(2n+1)}{2}\\ B=\frac{(2m+2-b)(b-1)}{2} t=max{t2−3t≤2(m−n)}b=n−ts=2n(n+1)(2n+1)B=2(2m+2−b)(b−1)
然后因为有二次项,会爆 long long,所以要开 __int128。
哦对还要预处理出 2 2 2 和 6 6 6 的逆元。
时间复杂度: O ( T log m ) O(T\log m) O(Tlogm)。
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ljl;
typedef __int128 i128;
void Rd(i128 &x);
void Wt(i128 x);
const i128 Mod=998244353,inv6=166374059,inv2=499122177;
int T;i128 n,m,ans,t,b;
bool chk(i128 x){return x*x-x*3ll<=(m-n)*2ll;}
void get_t()
{
i128 l=1,r=n,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(chk(mid))t=mid,l=mid+1;
else r=mid-1;
}
return;
}
i128 calc(i128 n){return n%Mod*(n+1ll)%Mod*(n*2ll+1)%Mod*inv6%Mod;}
i128 cc(i128 t){return (calc(t)+t*2ll-t*(t+1ll)*3ll%Mod*inv2%Mod)%Mod*inv2%Mod;}
void Main()
{
Rd(n);Rd(m);ans=0;
// for(t=1;chk(t);++t){}--t;
get_t();
// for(ljl i=2;i<=t;++i)ans=(ans+(i-1)*(i-2)/2+1)%Mod;
ans=(ans+t)%Mod;
ans=(ans+cc(t))%Mod;
//----------------------------------------------------
ans=(ans+t*(t-1)/2ll)%Mod;b=n-t;
ans=(ans+(m*2ll+2-b)*(b-1)/2)%Mod;
ans=((ans+Mod)%Mod)%Mod;
Wt(ans);putchar('\n');
return;
}
int main(){
scanf("%d",&T);
while(T--)Main();
return 0;
}
void Rd(i128 &x)
{
x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while('0'<=ch&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch-'0');
ch=getchar();
}
return;
}
void Wt(i128 x)
{
if(x>9)Wt(x/10);
putchar((x%10)+'0');
return;
}
在机房推完式子,草稿纸放机房了,回家又推了一遍......