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

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

 
回顾我的一生,一共有n个事件,每一个事件有一个幸福值p_i。
我想用n-1条线把所有的事件连起来,变成一个连通块。一条连接了事件x和事件y的线会产生min(p_x mod p_y,p_y mod p_x)的喜悦值。

日日重复同样的事,遵循着与昨日相同的惯例,若能避开猛烈的狂喜,自然也不会有悲痛的来袭。因此,我想知道连接起来之后产生喜悦值最小是多少

对于30%的数据,保证1<=n<=10^3。
对于另外40%的数据,保证1<=p_i<=10^6。
对于100%的数据,保证1<=n<=10^5,1<=p_i<=10^7。

给你n个点叫你求一个最小生成树

考虑贪心,我们先将所有的p排序去重(显然代价为0),让后对于每一个pi,枚举它的倍数kpi

我们对于每一个kpi找到一个比其大而且最小的pj,我们将i->j连一条边,边权为pj-kpi

让后直接跑MST即可

注意这题虽然有7s+3/4GB内存但是这样是会超时的

我们考虑两个优化

1.若pj-kpi>p1(p中最小的) 那么我们不连接i->j

显然,最坏情况下,我们依然可以每个点都和p[1]连接,这样最长边也会比p[1]要小

2.在找pj的时候,若有多个k满足p[j-1]<kp<=p[j]那么我们只保留k最大的那个,这里可以用二分加速

让后就是乱搞的mst即可,注意并查集记得写按秩合并

(另外不建议看上面开头提到的那部小说!特别针对某些人群,如果你已经进了省队请无视这句话~)

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define M 5000010
#define N 100010
#define P 10000000
using namespace std;
struct Edge{ int u,v,c; } G[M];
int n,m,s[N],t=0,f[N],r[N],v[M]; long long p=0;
inline bool c1(Edge a,Edge b){ return a.c<b.c; }
inline int gf(int x){ return x==f[x]?x:f[x]=gf(f[x]); }
inline void merge(int x,int y){
	if(r[x]>r[y]) f[y]=x;
	else {
		f[x]=y;
		if(r[x]==r[y]) ++r[y];
	}
}
int main(){
	freopen("autosadism.in","r",stdin);
	freopen("autosadism.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",s+i),f[i]=i,r[i]=1;
	sort(s+1,s+1+n); n=unique(s+1,s+1+n)-s-1; s[n+1]=1<<30;
	for(int i=1;i<=n;++i)
		for(int j=s[i],k=i+1;j<=P&&k<=n;j+=s[i]){
			if(s[k]<j) k=lower_bound(s+i,s+2+n,j)-s;
			if(k>n) break; j+=(s[k]-j)/s[i]*s[i];
			if(s[k]-j<=s[1]) G[t++]=(Edge){i,k,s[k]-j};
		}
	sort(G,G+t,c1);
	for(int u,v,i=0,k=1;k<n;++i){
		u=gf(G[i].u); v=gf(G[i].v);
		if(u!=v){ merge(u,v); k++; p+=G[i].c; }
	}
	printf("%lld\n",p);
}

posted @ 2017-11-02 20:09  扩展的灰(Extended_Ash)  阅读(106)  评论(0编辑  收藏  举报