[JZOJ5445]【NOIP2017提高A组冲刺11.2】失格

Description

胆小鬼连幸福都会害怕,碰到棉花都会受伤,有时还被幸福所伤。
——太宰治《人间失格》

回顾我的一生,一共有n个事件,每一个事件有一个幸福值p_i。
我想用n-1条线把所有的事件连起来,变成一个连通块。一条连接了事件x和事件y的线会产生min(p_x mod p_y,p_y mod p_x)的喜悦值。
日日重复同样的事,遵循着与昨日相同的惯例,若能避开猛烈的狂喜,自然也不会有悲痛的来袭。因此,我想知道连接起来之后产生喜悦值最小是多少。
对于100%的数据,保证1<=n<=10^5,1<=p_i<=10^7。

Solution

可以先把所有p相等的连起来,显然费用为0

对于一个点,考虑它连出去的边。
枚举Pi的倍数,找到离它右边最近的一个连起来。

正确性。。比较显然吧。

最多可以连出MaxP log P条边

然后直接Kruskal
随便去掉一些重复的应该就能够通过

排序的话用桶排也是可以的

毕竟Time limits 7s 还开O2

Code

#pragma GCC optimize(2)
#include 
#include 
#include 
#include 
#include 
#include 
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
#define M 10000005
#define LL long long
using namespace std;
int c[N],n,m,dis[N],f[N],m1,m2;
int lt[M],lt1[M];
LL ans;
bool bz[N];
struct node
{
    int x,y,c;
    friend bool operator <(node x,node y)
    {
        return x.c<y.c;
    }
}a[5*M];
int getf(int k)
{
    if(f[k]==k||f[k]==0) return k;
    return f[k]=getf(f[k]);
}
int main()
{
    cin>>n;
    int mx=0;
    fo(i,1,n) scanf("%d",&c[i]),mx=max(mx,c[i]);
    sort(c+1,c+n+1);
    m1=n-1;
    fo(i,1,n) 
    {
        if(c[i]!=c[i-1]) lt[c[i]]=i,lt1[c[i]-1]=i;
        if(c[i]==c[i+1]) m1--,f[getf(i+1)]=getf(i);
    }
    fod(i,mx,1) 
    {
        if(lt[i]==0) lt[i]=lt[i+1];
        if(lt1[i]==0) lt1[i]=lt1[i+1];
    }
    fo(i,1,n) 
    {
        if(c[i]!=c[i-1]) 
        {
            for(int j=1;c[i]*j<=mx;j++) 
            {
                if(j>1&&lt[c[i]*j]==lt[c[i]*(j-1)]) continue;
                int y=lt[c[i]*j];
                if(j==1) y=lt1[c[i]*j];
                if(y==0) continue;
                a[++m].c=c[y]%c[i];
                a[m].y=y,a[m].x=i;
            }
        }
    }
    sort(a+1,a+m+1);
    LL ans=0;
    for(int i=1;i<=m&&m1;i++)
    {
        int fx=getf(a[i].x),fy=getf(a[i].y);
        if(fx!=fy) ans+=a[i].c,f[fx]=fy,m1--;
    }
    printf("%lld",ans);
}

你可能感兴趣的:(题解,————最小生成树)