1. 程式人生 > 其它 >[COCI 2016-2017#6] Sirni

[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;
}
夜畔流離回,暗歎永無殿。 獨隱萬花翠,空寂亦難遷。 千秋孰能為,明滅常久見。 但得心未碎,踏遍九重天。