P12375 「LAOI-12」MST? 题解

P12375 「LAOI-12」MST? 题解

link.

前言:感谢 Billhqh9yjs0929 帮我查的神秘平方求和公式,不感谢 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;
}

在机房推完式子,草稿纸放机房了,回家又推了一遍......

相关推荐
雪度娃娃1 小时前
多用户任务管理器
c++·个人开发
_深海凉_1 小时前
LeetCode热题100-二叉树的直径
算法·leetcode·职场和发展
shylyly_1 小时前
大小端字节序
数据结构·算法·联合体·大小端字节序·字节序判断
mmz12071 小时前
深度优先搜索DFS3(c++)
c++·算法·深度优先
水蓝烟雨1 小时前
3373. 连接两棵树后最大目标节点数目 II
算法·leetcode
故事和你911 小时前
洛谷-【图论2-1】树6
开发语言·数据结构·c++·算法·深度优先·动态规划·图论
sali-tec1 小时前
C# 基于OpenCv的视觉工作流-章73-点-线距离
图像处理·人工智能·opencv·算法·计算机视觉
不知名的老吴1 小时前
在C++中不用宏怎么打日志的使用建议
开发语言·c++·算法
图码2 小时前
生命游戏的优雅解法:从O(mn)空间到O(1)空间的进阶之旅
数据结构·算法·游戏·矩阵·空间计算