题目大意
给你n个点,每个点有权值
pi
,一个点x和y连边的费用是
min(px%py,py%px)
,求原图最小生成树的费用。
n<=100000,
pi<=107
,时限7秒开O2。
分析
这道题乍地一看只会
O(n2)
的做法,而一般这种题就是优化边数然后用经典做法。
研究一下边费用的特征,发现肯定是大的模小的更小。两个p相同的点,直接并成1个点即可。
考虑p的值域只有
107
,我们尝试有关倍数的方法。
对于一个点x,我考虑他跟p更大的连边,大胆尝试把最接近
px+1,2px,3px...
的点和他的边都记录下来,然后连起来。然后就会发现这样连边不会漏掉最优解的任何一条边。
如何证明呢?考虑
apx和(a+1)px
之间的那些点,假如从小到大为
b1,b2...bm
。我们没有必要让x和除了
b1
的其他点连。
反证法:我们给x和
b1和bk
都连。按照刚刚的方法b之间两两都有连边。那么x和
bk
的边费用是
pbk%px
的,然而让x和b1,b1和b2…b_k-1和b_k都连边的费用也是这么大,这条边显然没什么用。
正确性证完了,我们分析一下这样的边数多少。弄一个松一点的上界:
∑ni=1107/i=107∑ni=11/i≈107×ln(n)
。实际上是远远达不到这个大小的,放心地开够空间限制大小的边集数组,排序用桶排,或者直接插进链表里,就可以过了。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmin(a,b) (a=(a<b)?a:b)
#define cmax(a,b) (a=(a>b)?a:b)
const int N=1e5+5,M=1e7+2e6+5;
struct rec
{
int x,y;
}b[M*3];
int a[N],fa[N],i,j,tb,n,ri[M],mx,pd[M],tmp,lst,x,fst[M],nxt[M*3],b1[M*3],p,tt,k;
ll ans;
void cr(int x,int y)
{
tt++;
b1[tt]=y;
nxt[tt]=fst[x];
fst[x]=tt;
}
int get(int x)
{
if (fa[x]==x) return x;
return fa[x]=get(fa[x]);
}
int main()
{
freopen("t3.in","r",stdin);
// freopen("autosadism.out","w",stdout);
scanf("%d",&n);
fo(i,1,n)
{
scanf("%d",a+i);
cmax(mx,a[i]);
pd[a[i]]=i;
}
fd(i,mx,1)
{
if (pd[i]) tmp=i;
ri[i]=tmp;
}
fo(i,1,mx)
{
if (pd[i])
{
lst=0;
for (j=i;j<=mx;j+=i)
{
x=ri[j+(i==j)];
if (x!=lst)
{
cr(x%i,++tb);
b[tb].x=pd[i];
b[tb].y=pd[x];
lst=x;
}
}
}
}
fo(i,1,n) fa[i]=i;
fo(k,0,mx)
{
for(p=fst[k];p;p=nxt[p])
{
i=b1[p];
if (get(b[i].x)!=get(b[i].y))
{
ans+=k;
fa[get(b[i].x)]=get(b[i].y);
}
}
}
printf("%lld",ans);
}