AtCoder Beginner Contest 210
E - Ring MST
题目描述:
n个点,m种操作,每种操作都有一个
a, c
,即你可以选择任意一个点x
和(x+a)%n
连一条长度为c
的边,每种操作都可以进行任意次,顺序也可以任意,问把n个点连成一个连通图的最小权值是多少
思路:
这个题巨有意思!
显然是一个最小生成树的题,但是克鲁斯卡尔的复杂度是O(mlongm)
,这里的m
指的是边的数量,也就是点的数量减1,即n-1
,这个题的n<=1e9
,显然不能做,所以我们考加速克鲁斯卡尔的合并过程,克鲁斯卡尔的过程无非是对边排序,然后判断是否在一个连通块,然后连边,而题这里我们把m
种操作看成m
种边,每种边可能会连很多个不同的点,也就会导致合并巨慢。我们考虑每种连边方式能连接的边的数量是:使用该边
i
后的连通块的数量 - 使用该边i
前的连通块的数量。那问题又来了,怎么计算使用该边后的连通块的数量?
(
官方题解写的非常非常好,难得我能看懂一次官方题解如果
u
和v
可以通过前t
条边相连,也就是存在一组解 ( k 1 , k 2 . . . k t ) (k_1,k_2...k_t) (k1,k2...kt) 满足: v ≡ u + k 1 A 1 + k 2 A 2 + . . . + k t A t ( m o d n ) v ≡ u + k_1A_1+k_2A_2+...+k_tA_t(mod\space n) v≡u+k1A1+k2A2+...+ktAt(mod n)也就是存在一组解 ( k 0 , k 1 , k 2 . . . k t ) (k_0,k_1,k_2...k_t) (k0,k1,k2...kt),满足:
v = u + k 0 ∗ n + k 1 A 1 + k 2 A 2 + . . . + k t A t v = u + k_0*n + k_1A_1+k_2A_2+...+k_tA_t v=u+k0∗n+k1A1+k2A2+...+ktAt
我们令 d = g c d ( n , A 1 , A 2 . . . A t ) d = gcd(n,A_1,A_2...A_t) d=gcd(n,A1,A2...At)
则 v = u + k ∗ d v = u + k*d v=u+k∗d
即 v ≡ u ( m o d d ) v ≡ u (mod\space d) v≡u(mod d)
也就是存在
d
个连通块,不连通的这d
个就是0
到d - 1
这d
个点分别所在的连通块所以我们可以对边的权值从小到大排序,然后用一个变量记录当前剩余的连通块的数量,再求
gcd(n, a1, a2...ai)
,二者相减就得到该边用的次数,乘上权值就行
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inf 0x3f3f3f3f
#define mod7 1000000007
#define mod9 998244353
#define m_p(a,b) make_pair(a, b)
#define mem(a,b) memset((a),(b),sizeof(a))
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define debug(a) cout << "Debuging...|" << #a << ": " << a << "\n";
typedef long long ll;
#define int long long
typedef pair <int,int> pii;
#define MAX 300000 + 50
int n, m, k, x;
struct ran{
int a, c;
bool operator < (const ran &x)const{
return c < x.c;
}
}tr[MAX];
int gcd(int a, int b){
if(!b)return a;
else return gcd(b, a % b);
}
void work(){
cin >> n >> m;
for(int i = 1; i <= m; ++i)cin >> tr[i].a >> tr[i].c;
sort(tr + 1, tr + 1 + m);
ll ans = 0;
int sum = n;
int gg = n;
for(int i = 1; i <= m; ++i){
gg = gcd(gg, tr[i].a);
ans += (sum - gg) * tr[i].c;
sum = gg;
if(gg == 1)break;
}
if(gg == 1)cout << ans << endl;
else cout << -1 << endl;
}
signed main(){
io;
work();
return 0;
}