[COCI 2016-2017#6] Sirni

\(\text{Problem}:\)Sirni

\(\text{Solution}:\)

\(P_{a}>P_{b}\) 时,连接 \(a,b\) 的代价即为 \(P_{a}\text{ mod }P_{b}\)。这样去掉了 \(\min\)

首先将权值相同的点合并。对于权值为 \(x\)​ 的点,我们只需对于每个 \(k\)​,找到满足 \(kx\leq w<(k+1)x\)​ 的最小的 \(w\)​(当 \(k=1\)​ 时为 \(x<w<2x\)​),添加一条连接 \(x\)​ 与 \(w\)​ 的边即可。简单证明:假设存在 \(w_{2}\)​,满足 \(kx\leq w<w_{2}<(k+1)x\)​ 且权值区间 \((w,w2)\)​​ 中没有结点(否则是一个子问题),那么根据上面的连边方式,一定存在 \(x\rightarrow w\)​ 与 \(w\rightarrow w_{2}\)​ 这两条边,而这两边的权值和与 \(x\rightarrow w_{2}\)​​ 这条边的权值相同。故连 \(x\rightarrow w_{2}\)​ 一定不会更优。

总边数为 \(O(V\log V)\)​,而边权最大值小于 \(V\),故利用用桶排序然后求出最小生成树即可。总时间复杂度 \(O(n+V\log V\cdot \alpha(V))\),可以通过。

\(\text{Code}:\)

#include <bits/stdc++.h>
//#pragma GCC optimize(3)
//#define int long long
#define ri register
#define mk make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define is insert
#define es erase
#define vi vector<int>
#define vpi vector<pair<int,int>>
using namespace std; const int N=10000010;
inline int read()
{
	int s=0, w=1; ri char ch=getchar();
	while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48), ch=getchar();
	return s*w;
}
int n,a[N],up,f[N],lar[N]; long long ans;
vpi g[N];
inline int Find(int x) { return f[x]^x?f[x]=Find(f[x]):x; }
signed main()
{
	n=read();
	for(ri int i=1;i<=n;i++)
	{
		int x=read();
		a[x]++, up=max(up,x);
		lar[x]=x;
	}
	for(ri int i=up-1;i;i--)
	{
		if(!lar[i]) lar[i]=lar[i+1];
	}
	for(ri int i=1;i<=up;i++)
	{
		if(!a[i]) continue;
		for(ri int j=i;j<=up;j+=i)
		{
			int l=(j==i)?(i+1):(j), r=min(j+i-1,up);
			if(lar[l]<=r) g[lar[l]%i].eb(mk(i,lar[l]));
		}
	}
	for(ri int i=1;i<=up;i++) f[i]=i;
	for(ri int i=0;i<=up;i++)
	{
		for(auto j:g[i])
		{
			int x=Find(j.fi), y=Find(j.se);
			if(x==y) continue;
			ans+=i, f[x]=y;
		}
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-08-10 14:53  zkdxl  阅读(73)  评论(0编辑  收藏  举报