AtCoder keyence2019_e

洛谷的AtC接口貌似坏掉了,水不了这道紫题了,但是题解还是可以水的(大雾

洛谷题目页面传送门 & AtC题目页面传送门

(n)个点,第(i)个点的权值为(a_i)。给定常数(d),在点(i,j)之间连边的代价是(|i-j|d+a_i+a_j)。求将它们连成一棵树的最小代价。

(ninleft[1,10^5 ight])

首先不难发现,原题相当于,求完全图的MST的权,其中点(i,j)之间的边权为(|i-j|d+a_i+a_j)。如果直接Kruskal的话,建图就要爆炸,边数是(mathrm O!left(n^2 ight))的。考虑用某些MST的奇技淫巧。

我们的目的是,尽可能删掉一些边,留下尽量少的边,使得MST的权不变。

注意到这样一条经典的性质:MST不可能包含一个环上所有的最大边。证明很简单:反证法。若包含,则随意去掉一个环上的最大边((x,y)),此时整棵树断成两部分,(x,y)在不同的部分。由于是个环,那么环上一定有一条反向的不包含 ((x,y))(x o y)路径,连接两个部分,这条路径上显然有连接两部分的另一条边,选上它得到另一棵生成树。这条边边权严格小于((x,y))的权,所以原树不可能是MST。得证。

不妨研究一些环,看能不能用这个性质删边。从最简单的三元环入手。

首先,(i<j)间边权可变形为(a_i-id+a_j+jd)。令(A_i=a_i-id,B_i=a_i+id),那么就是(A_i+B_j)。对于三元环((i,j,k))

  1. (i<j<k)时,((i,j))非严格最大当且仅当(B_jgeq B_k,A_i+B_jgeq A_j+B_k)。注意到(B_jgeq B_k,A_igeq A_j)显然是它的充分条件,我们完全可以只用这个,正确性健在;
  2. (i<k<j)时,((i,j))非严格最大当且仅当(B_jgeq B_k,A_igeq A_k)
  3. (k<i<j)时,((i,j))非严格最大当且仅当(A_i+B_jgeq A_k+B_i,A_igeq A_k)。同(1),充分条件是(B_jgeq B_i,A_igeq A_k)

(1,2)放一起研究,可以得出共同点:(B_jgeq B_k,A_igeq A_{ ext{位置在第2的点}})。那么我们想,对于一堆在(i)右边的、(A)(leq A_i)的点,我们一定可以把它们连向(i)的边互相毙,毙的只剩一个,即那个(B)值最小的。

这看起来很成立,但是似乎仅在严格的时候成立,因为根据上述性质,只有最大边唯一的时候,才可以肆无忌惮地毙掉最大边,否则一般情况下需要保证其他最大边健在。不过在这里的特殊情况,即使非严格也是成立的。证明:若不存在任意一个MST使得它包含任意一条被毙掉的边则已,否则选择一个:设留下来的点是(x)。对于任意一个被毙掉的还在MST里的边((i,y)),若((x,y))不在MST里即可直接换成((x,y)),否则((i,x))一定不在(因为生成树里不可能有环),但是不能直接换成((i,x)),因为它是唯一的,而有那么多被毙掉的边等着换呢。如此,只要证满足((i,y),(x,y))都在MST里的被毙掉的边((i,y))最多只有一个。这是显然的,因为若有两个(y_0,y_1),则((i,x,y_0,y_1))构成四元环。得证。

同理,一堆在(i)左边的、(B)(leq B_i)的点,我们也可以留下(A)值最小的,其他都毙掉。

不过这样,两个毙法一综合,问题又出现了。。有的点对((i,j)(i<j)),它们之间的边可能会被考虑两次((j)左边和(i)右边),这样就可能是即被删掉,又被留下来的状态。这里有一个巧妙的处理方法:观察(A_igeq A_j),展开得(a_i-idgeq a_j-jd),注意到因为(i<j),所以(-id>-jd),那么充分条件就是(a_igeq a_j)。同理,(B_jgeq B_i)的充分条件是(a_jgeq a_i)。在(a)值互不相同的时候,两者恰好只能选一个,也就是说完全图里每条边恰好被考虑了一遍,就不存在问题了。如果(a)值有相同的呢?只需要再加第二个关键字:位置,这样就一定能比出大小了。

如此,每个点两侧最多各有一条边留下来,边数复杂度(mathrm O(n))。再用BIT xjb随便维护一下建图,然后Kruskal,时间复杂度(mathrm O(nlog n))

代码也没啥需要说的:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
#define X first
#define Y second
#define pb push_back
const int inf=0x3f3f3f3f3f3f3f3f;
int lowbit(int x){return x&-x;}
const int N=200000;
int n,d;
int a[N+1];
pair<int,int> p[N+1];
struct bitree{
	pair<int,int> mn[N+1];
	void init(){
		for(int i=1;i<=n;i++)mn[i]=mp(inf,0);
	}
	void chkmn(int x,pair<int,int> v){
		while(x<=n)mn[x]=min(mn[x],v),x+=lowbit(x);
	}
	int Mn(int x){
		pair<int,int> res(inf,0);
		while(x)res=min(res,mn[x]),x-=lowbit(x);
		return res.Y;
	}
}bit,bit_r;
vector<pair<int,int> > eg;
struct ufset{//并查集 
	int fa[N+1];
	void init(){memset(fa,0,sizeof(fa));}
	int root(int x){return fa[x]?fa[x]=root(fa[x]):x;}
	bool mrg(int x,int y){
		x=root(x);y=root(y);
		if(x==y)return false;
		return fa[x]=y,true;
	}
}ufs;
int cst(pair<int,int> x){//边权 
	if(x.X>x.Y)swap(x.X,x.Y);
	return a[x.X]+a[x.Y]+(x.Y-x.X)*d;
}
bool cmp(pair<int,int> x,pair<int,int> y){
	return cst(x)<cst(y);
}
int kruskal(){//Kruskal 
	sort(eg.begin(),eg.end(),cmp);
	int ans=0;
//	for(int i=0;i<eg.size();i++)printf("%lld %lld
",eg[i].X,eg[i].Y);
	for(int i=0;i<eg.size();i++)ans+=ufs.mrg(eg[i].X,eg[i].Y)*cst(eg[i]);
	return ans;
}
signed main(){
	cin>>n>>d;
	for(int i=1;i<=n;i++)scanf("%lld",a+i),p[i]=mp(a[i],i);
	sort(p+1,p+n+1);
	bit.init();bit_r.init();
	for(int i=1;i<=n;i++){//BIT建图 
		int mn1=bit.Mn(p[i].Y-1),mn2=bit_r.Mn(n-p[i].Y);
		if(mn1)eg.pb(mp(p[i].Y,mn1));
		if(mn2)eg.pb(mp(p[i].Y,mn2));
		bit.chkmn(p[i].Y,mp(p[i].X-p[i].Y*d,p[i].Y));bit_r.chkmn(n-p[i].Y+1,mp(p[i].X+p[i].Y*d,p[i].Y));
	} 
	cout<<kruskal();
	return 0;
}

官方题解还给出了另一种分治的方法。就是说,把边分成在左半边内、在右半边内、横跨两半边三类,处理横跨两半边的,另外两种递归下去。考虑如何处理。我们选择左半边(A)值最小的点(x)连所有右半边的点,选择右半边(B)值最小的点(y)连所有左半边的点,这样一来其他所有的横跨两半边的边((x',y'))都被毙掉了,因为((x,y),(x,y'),(x',y))都在,((x',y'))成了环中最大的边。至于严格非严格的问题,xjb再证一下即可。这是在四元环上作文章的。边数复杂度(mathrm O(nlog n)),时间复杂度(mathrm O!left(nlog^2n ight))

代码没写。应该很好写?

综上所述,下次遇到这样的人类智慧题我还是做不出来。

原文地址:https://www.cnblogs.com/ycx-akioi/p/AtCoder-keyence2019-e.html