Connecting Cities[AT4569][最小生成树优化]

文章目录

题目

Luogu
在这里插入图片描述

思路

假设所有 A i A_i Ai 不同,那么一条边肯定是 A A A 一边小,一边大
假设 j < i , A j < A i j<i,A_j<A_i j<i,Aj<Ai 并且此时 j j j i i i 在左边值比 A i A_i Ai 小的最小边
尝试证明只用保留这种边 O ( n ) O(n) O(n)
当存在 k k k 满足 A k > A i A_k>A_i Ak>Ai 我们认为此时是 i i i 的选择方式,和 k k k 无关
i < k i<k i<k 时候是右边一条边的问题

也就是现在有两个问题
已知 ( j , i ) < ( k , i ) (j,i)<(k,i) (j,i)<(k,i)
A j , A k < A i A_j,A_k<A_i Aj,Ak<Ai
那么 A j − j ∗ D < A k − k ∗ D A_j-j*D<A_k-k*D AjjD<AkkD
尝试证明 ( i , k ) (i,k) (i,k) 是这个三元环中最大的一条边
k k k j j j 左边易证
j < k < i j<k<i j<k<i 时候
分析法后步骤:
A i + i D > A k + k D A_i+iD>A_k+kD Ai+iD>Ak+kD
A i + i D − k D > A k + k D − k D > A j − j D + k D A_i+iD-kD>A_k+kD-kD>A_j-jD+kD Ai+iDkD>Ak+kDkD>AjjD+kD
( k , i ) > ( j , i ) (k,i)>(j,i) (k,i)>(j,i)
然后根据破圈定理可得这条边一定不选
等于时候可知最大边随便一条不选就行了
右边同理
然后就只需要树状数组维护前缀最小值和对应下标
边数 O ( n ) O(n) O(n) 时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

代码

#include<set>    
#include<map>    
#include<stack>    
#include<ctime>    
#include<cstdio>    
#include<queue>    
#include<cmath>    
#include<vector>    
#include<cstring>   
#include<climits>    
#include<iostream>   
#include<algorithm>
using namespace std;
#define LL long long
int read(){
    int f=1,x=0;char c=getchar();
    while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}
    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();}
    return f*x;
}
#define MAXN 200000
#define mp make_pair
#define INF 10000000000000000ll
int n,m;
pair<LL,int> tr[MAXN+5];
int lowbit(int x){return x&-x;}
void Init(){
	for(int i=1;i<=m;i++)
		tr[i]=mp(INF,0);
	return ;
}
void Add(int i,pair<LL,int> x){
	while(i<=m)
		tr[i]=min(tr[i],x),i+=lowbit(i);
	return ;
}
pair<LL,int> Que(int i){
	pair<LL,int> ret=mp(INF,0);
	while(i)
		ret=min(ret,tr[i]),i-=lowbit(i);
	return ret;
}
LL D,a[MAXN+5],b[MAXN+5];
struct E{
	int u,v;
	LL w;
	friend bool operator < (E a,E b){return a.w<b.w;}
}edge[2*MAXN+5];
int fa[MAXN+5];
int Find(int x){return fa[x]==x?x:(fa[x]=Find(fa[x]));}
int main(){
	n=read(),D=read();
	for(int i=1;i<=n;i++)
		a[i]=b[i]=read();
	sort(b+1,b+n+1);
	m=unique(b+1,b+n+1)-(b+1);
	for(int i=1;i<=n;i++)
		a[i]=lower_bound(b+1,b+m+1,a[i])-b;
	int ecnt=0;
	Init();
	for(int i=1;i<=n;i++){
		pair<LL,int> t=Que(a[i]);
		if(t.second)
			edge[++ecnt]=(E){i,t.second,t.first+b[a[i]]+i*D};
		Add(a[i],mp(b[a[i]]-i*D,i));
	}
	Init();
	for(int i=n;i>=1;i--){
		pair<LL,int> t=Que(a[i]);
		if(t.second)
			edge[++ecnt]=(E){i,t.second,t.first+b[a[i]]-i*D};
		Add(a[i],mp(b[a[i]]+i*D,i));
	}
	sort(edge+1,edge+ecnt+1);
	for(int i=1;i<=n;i++)
		fa[i]=i;
	LL ans=0;
	for(int i=1;i<=ecnt;i++){
		int u=edge[i].u,v=edge[i].v;
		LL w=edge[i].w;
		int fu=Find(u),fv=Find(v);
		if(fu!=fv){
			fa[fu]=fv;
			ans+=w;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值