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

题目

给定n个点,第i个点的权值为p[i],任意两点x,y都有边,边权为 min(px%py,py%px) 。问n个点的最小生成树的大小。

数据范围

对于30%的数据,保证1<=n<=10^3。
对于另外40%的数据,保证1<=p_i<=10^6。
对于100%的数据,保证1<=n<=10^5,1<=p_i<=10^7。
时间限制7s,空间限制768MB。

题解

条件

①每条边的边权为 min(px%py,py%px)
②总共要选n-1条边构造mst。

困惑点

①不知道究竟选哪条边作为mst(选的边没有标准)选的边有可能都很小且属于同个连通块。
min(px%py,py%px) 并不是一个确定的值,它依 px py 而定。

突破口

①有一个显然的结论,如果p相等,可以缩成一个点。(当时为什么没有去打?因为觉得那些边可能会属于不同的连通块,然而这是不可能的,设有c个点的p相等,不管怎么样都会选出c-1条权值为0的边连接这个连通块)听说这样可以多拿30分!!!!
②我们想去掉边权中的 min ,可以发现边权一定为 px%py ,如果 px>py 。但我一直在想为什么 px%py<py=py%px 我蠢啊!!这不是显然吗!!!
这样就去掉min了。
③根据②,为了方便去掉min,统一设 x<y 时, px<py 。所以将p从小到大排序。

主要思路

如果实在不会做,也可以猜一下结论:设点i为 pi ,对于每一个>1的整数x,找 xpi 的最小的 pj ,这些边 (i,j) 一定能构成mst。
这样就AC了?为什么!!!
这个在赛场上怎么证?正着证似乎不好证,那就用反证法。
假设有一条边 (a,b) 不满足条件,那么必定存在 xpapb<(x+1)pa
那么存在一系列点 c1,c2,...,ck 使得 xpapc1<pc2<...<pck<pb
那么根据题意我们一定会选择 (a,c1),(c1,c2),...,(ck1,ck),(ck,b) 。选这些边的总代价恰好为 pb%a 。选择这些边组成了一条路径,这个路径可能存在环,从而利用环切性质删掉一条边,所以这样的生成树可能比它优,但一定不会比他劣。

具体做法

由于n,p很大,怎么快速地知道最靠近 xpi 的数是哪一个呢?用一个桶, t[i] 表示最靠近i的p值。从后往前做一遍就好了。
边数很大且P相对于时间复杂度很小,用桶排好过快排。

总结

①对于mst问题,最难的点就是选择mst的边没有标准。这样子的解决办法大概有:
     ⑴将问题分成若干个子问题,每个子问题求出一种最优解,再合并。
     ⑵将边的范围缩小,然后尝试证明。当然,比赛时没有头绪的情况下可以凭借这个赌一把(本题就用这个方法)。
②如果权值带式子(带式子的东西最麻烦),能够化简或者消掉什么max,min之类的就消掉。如果像 px<py 就能将什么max,min消掉,直接对p进行排序,编号小的p值小,方便化简权值。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 100010
#define LL long long
#define lim 10000010
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
    int p,x,y;
};note a[N*400];
int i,j,k,l,n,m,cnt,x,mx;
int tot,gx,gy;
int s[N],o[lim];
int f[N];
int t[lim],t1[lim],t2[lim];
int odr[N*400];
LL ans;
int read(){
    int fh=1,res=0;char ch;
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')res=res*10+ch-'0',ch=getchar();
    return res*fh;
}
int get(int x){
    return x==f[x]?x:f[x]=get(f[x]);
}
int main(){
    n=read();
    fo(i,1,n)s[i]=read(),mx=max(mx,s[i]);
    sort(s+1,s+n+1);
    cnt=unique(s+1,s+n+1)-s-1;n=cnt;
    fo(i,1,n)t[s[i]]=s[i],o[s[i]]=i;
    fd(i,lim-2,0)if(!t[i]){
        t[i]=t[i+1];
        o[i]=o[i+1];
    }
    fo(i,1,n-1){
        a[++tot].x=i;a[tot].y=o[s[i+1]];a[tot].p=s[i+1]%s[i];
        j=s[i];
        while(j<lim-10){
            if(!t[j])break;
            if(!t[j+s[i]])o[j+s[i]]=n,t[j+s[i]]=mx;
            if(j<=lim && t[j]!=t[j+s[i]]){
                a[++tot].x=i;
                a[tot].y=o[t[j+s[i]]];
                a[tot].p=s[o[t[j+s[i]]]]%s[i];
            }
            j+=s[i];
        }
    }
    memset(t1,0,sizeof(t1));
    fo(i,1,tot)t1[a[i].p]++;
    fo(i,1,mx)t1[i]+=t1[i-1];
    fo(i,1,tot)odr[t1[a[i].p]--]=i;
    fo(i,1,n)f[i]=i;cnt=0;
    fo(i,1,tot){
        gx=get(a[odr[i]].x);
        gy=get(a[odr[i]].y);
        if(gx!=gy){
            ans=ans+(LL)a[odr[i]].p;
            f[gy]=gx;
            cnt++;
            if(cnt==n-1)break;
        }
    }
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值