【JZOJ 5445】【NOIP2017提高A组冲刺11.2】失格

Description

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

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

Solution

显然,相同的可以直接合并,
对于每一个 ai 枚举倍数k,对于 aj ,满足 kaiaj<(k+1)ai ,那么这些数,只有最小的一个是可能与i有边相连的,
那么直接预处理,排序后做Kruscal即可,不过由于边太多了,要用桶排,

复杂度: O(107log(107))

Code

我的程序为正解的阉割版,很难卡掉,但空间极其小(然并卵)

#include <cstdio>
#include <algorithm>
#include <queue>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef long long LL;
const int N=100500,mo=998244353;
int read(int &n)
{
    int w=1;char ch=' ';n=0;
    for(;ch!='-'&&(ch<'0'||ch>'9');ch=getchar());
    if(ch=='-')ch=getchar(),w=-1;
    for(;ch<='9'&&ch>='0';ch=getchar())n=n*10+ch-48;
    return n=n*w;
}
int n,m,ans;
int a[N];
int d[N][2],d0;
int g[N],bv[N];
bool z[N];
struct qqww
{
    int v,i;
    friend bool operator <(qqww q,qqww w){return q.v>w.v;}
};
priority_queue<qqww> Ds;
int gf(int q){return g[q]==q?q:(g[q]=gf(g[q]));}
bool PX(int q,int w){return q>w;}
int FD(int q,int t)
{
    while(a[q]<t)
    {
        int i=1;
        for(;(i<<1)<=q&&a[q-(i<<1)]<=t;i<<=1);
        q-=i;
    }
    return q;
}
void PRE()
{
    fo(i,2,n)
    {
        // printf("%d\n",i);
        int t=a[i];bv[i]=2e9;
        for(int j=FD(i-1,t);j>0;t+=a[i],j=FD(j-1,t))
        {
            if(a[j]-t>=a[i])
            {
                for(t=(a[j]/a[i])*a[i];a[j]-t>=a[i];t+=a[i]);
            }
            if(bv[i]>a[j]-t)
            {
                bv[i]=a[j]-t;
                if(!bv[i])break;
            }
        }
        qqww t1;t1.i=i;t1.v=bv[i];
        Ds.push(t1);
    }
}
int main()
{   
    freopen("autosadism.in","r",stdin);
    freopen("autosadism.out","w",stdout);
    int q,w;
    read(n);
    fo(i,1,n)read(a[i]);
    sort(a+1,a+1+n,PX);a[0]=2e9;
    if(a[n]<2){printf("0\n");return 0;}
    q=0;
    fo(i,2,n)
    {
        a[i-q]=a[i];
        if(a[i]==a[i-q-1])q++;
    }
    n-=q;
    ans=0;
    fo(i,1,n)g[i]=i;
    PRE();
    for(int I=1;I<n;)
    {
        // printf("%d\n",I);
        q=(Ds.top()).i;Ds.pop();
        int t=a[q],bt=bv[q];bv[q]=2e9;
        gf(q);
        for(int j=FD(q-1,t);j>0;t+=a[q],j=FD(j-1,t))
        {
            if(a[j]-t>=a[q])
            {
                for(t=(a[j]/a[q])*a[q];a[j]-t>=a[q];t+=a[q]);
            }
            if(g[q]!=gf(j))
            {
                if(a[j]-t<=bt)
                {
                    I++;
                    ans+=bt;
                    g[gf(q)]=g[j];
                    gf(q);
                }else if(bv[q]>a[j]-t)bv[q]=a[j]-t;
            }
        }
        qqww t1;t1.i=q;t1.v=bv[q];
        if(bv[q]<1e9)Ds.push(t1);
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值