Codeforces 2081D MST in Modulo Graph / Luogu P7789 [COCI 2016/2017 #6] Sirni 题解 [ 蓝 ] [ 最小生成树 ] [ MST 优化建图 ] [ 调和级数 ]
MST in Modulo Graph:MST 好题。
一般这种完全图求 MST 的题,有两种思路:
- 优化建图,根据图的性质砍掉一些无用边。
- 使用 Boruvka 算法,搭配某些数据结构求每个节点的最小边。
本题中取模的条件用数据结构比较难维护,于是考虑优化建边的思路。
首先,值相同的点显然是可以先缩掉的,因为它们之间的边权值为 \(0\)。
接下来对每一个节点 \(a_i\) 考虑它作为模数时的边,然后枚举模数的所有倍数 \(ka_i\),发现当两个数 \(x,y\) 满足 \(ka_i < x < y \le (k+1)a_i\) 的时候,连 \((a_i,x)\) 一定比 \((a_i,y)\) 更优。因为 \((x,y)\) 的权值是 \(y - x\),而 \((a_i,y)\) 的权值为 \(y - ka_i\),显然更大。于是我们可以连接 \((a_i, x), (x, y)\),比 \((a_i, x), (a_i, y)\) 更优。
建图后跑任意一个最小生成树算法即可。因为枚举每个数的倍数是调和级数复杂度,所以时间复杂度是跑不满的 \(O(V\log^2 V)\)。
对于 CF2081D 中,此题 \(n, V\) 同阶,不用考虑一些复杂度的问题。而对于 P7789,此时 \(n,V\) 不同阶,复杂度就不是严格的 \(O(n\log^2 n)\) 了。严格的计算比较复杂,这里我们可以分段计算,大概估算一下边的上界。具体地,分段计算 \(10^i\sim 10^{i + 1}\),这段区间内的边数上界是 \(10^{i + 1}\min(n, \dfrac{V}{10^{i + 1}})\)。将 \(i = 0\sim 6\) 带入,得到一个宽松的边数上界:\(6.1\times 10^7\)。空间和时间都比较卡,但是足以通过了。
CF2081D:
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 500005, M = 20000005;
int n, m, a[N];
struct Edge{
int u, v;
ll w;
bool operator < (const Edge & t) const{
return (w < t.w);
}
}e[M];
int getnxt(int x)
{
return (lower_bound(a + 1, a + n + 1, x) - a);
}
int fa[N];
void init()
{
for(int i = 1; i <= n; i++) fa[i] = i;
}
int findf(int x)
{
if(fa[x] != x) fa[x] = findf(fa[x]);
return fa[x];
}
void combine(int x, int y)
{
int fx = findf(x), fy = findf(y);
fa[fx] = fy;
}
void solve()
{
cin >> n;
m = 0;
for(int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + n + 1);
n = unique(a + 1, a + n + 1) - a - 1;
for(int i = 1; i <= n; i++)
{
for(int j = a[i]; j <= a[n]; j += a[i])
{
int v = getnxt(j);
if(j == a[i]) v = getnxt(j + 1);
if(v > n || j / a[i] != a[v] / a[i]) continue;
e[++m] = {i, v, a[v] % a[i]};
}
}
sort(e + 1, e + m + 1);
init();
ll ans = 0;
for(int i = 1; i <= m; i++)
{
int u = e[i].u, v = e[i].v; ll w = e[i].w;
if(findf(u) != findf(v))
{
combine(u, v);
ans += w;
}
}
cout << ans << "\n";
}
int main()
{
//freopen("sample.in", "r", stdin);
//freopen("sample.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--) solve();
return 0;
}
P7789:
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 500005, M = 61000005;
int n, m, a[N];
struct Edge{
int u, v, w;
bool operator < (const Edge & t) const{
return (w < t.w);
}
}e[M];
int getnxt(int x)
{
return (lower_bound(a + 1, a + n + 1, x) - a);
}
int fa[N];
void init()
{
for(int i = 1; i <= n; i++) fa[i] = i;
}
int findf(int x)
{
if(fa[x] != x) fa[x] = findf(fa[x]);
return fa[x];
}
void combine(int x, int y)
{
int fx = findf(x), fy = findf(y);
fa[fx] = fy;
}
void solve()
{
cin >> n;
m = 0;
for(int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + n + 1);
n = unique(a + 1, a + n + 1) - a - 1;
for(int i = 1; i <= n; i++)
{
for(int j = a[i]; j <= a[n]; j += a[i])
{
int v = getnxt(j);
if(j == a[i]) v = getnxt(j + 1);
if(v > n || j / a[i] != a[v] / a[i]) continue;
e[++m] = {i, v, a[v] % a[i]};
}
}
sort(e + 1, e + m + 1);
init();
ll ans = 0;
for(int i = 1; i <= m; i++)
{
int u = e[i].u, v = e[i].v; ll w = e[i].w;
if(findf(u) != findf(v))
{
combine(u, v);
ans += w;
}
}
cout << ans << "\n";
}
int main()
{
//freopen("sample.in", "r", stdin);
//freopen("sample.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
while(t--) solve();
return 0;
}

浙公网安备 33010602011771号