Beautiful Permutation - Editorial

About the Problem

Setter(s) Jatin Garg
Tester(s) Vichitr Gandas, Mayank Pugalia, Aswin Ashok
Difficulty Medium
Topics Graph, Observations, Greedy, MST, Boruvka’s Algorithm
Practice Link
Contest Link

Solution Idea

Permutation is nothing but set of disjoint cycles.

In beautiful permutation there is only one single cycle of size n .

Observations

  • Swapping any two indexes either merge two cycles or split some cycle.
  • Obviously splitting any cycle will always be non-optimal, so we will consider only merging of some cycles.
  • So we some K cycles, we have to merge them into one using minimum cost, this problem is almost same as finding MST , by assuming cycles as nodes.

There will be O(n^2) edges so we can not use any standard algorithm to find MST.

Now there are two methods to find MST.

Method 1: Greedy Algorithm

  • Assume indexes (i,j,k,l) were involved in swap operation, and assume C_i \leq C_j \leq C_k \leq C_l .
  • Now there will not be any set of edges in MST , which includes both (C_i,C_k) and (C_j,C_l) . Instead of these two edges, we can select (C_i,C_j) and (C_k,C_l) .
  • So just sort all the cost_i 's and add edge between two consecutive indexes in sorted order if they are not in same cycle.
  • Now just build the MST with these edges.

Complexity Analysis:

  • Time Complexity: O(n \log{n})
  • Space Complexity: O(n)

Method 2: Boruvka’s Algorithm

  • This method involves a non-standard algorithm Boruvka’s Algorithm . We can solve much more complex cost function using this algorithm.
  • In starting suppose there are n components, then in every iteration we find best outgoing edge for every component using some data structure such as C++ Set , and these edges into the MST, after every iteration Number of components will be halved, hence there will be O(\log{n}) iterations, every iteration costs O(n \log{n}) ,so Time Complexity would be O(n \log^2{n}) .

Complexity Analysis:

  • Time Complexity: O(n \log^2{n})
  • Space Complexity: O(n \log{n})

Codes

Setter's Code
// Jai Shree Ram  
  
#include<bits/stdc++.h>
using namespace std;

#define rep(i,a,n)     for(int i=a;i<n;i++)
//#define int            long long
#define x              first
#define y              second
#define pii            pair<int,int>


template<typename T1,typename T2>istream& operator>>(istream& in,pair<T1,T2> &a){in>>a.x>>a.y;return in;}
template<typename T1,typename T2>ostream& operator<<(ostream& out,pair<T1,T2> a){out<<a.x<<" "<<a.y;return out;}
template<typename T,typename T1>T maxs(T &a,T1 b){if(b>a)a=b;return a;}
template<typename T,typename T1>T mins(T &a,T1 b){if(b<a)a=b;return a;}

const int maxn = 2e6 + 5;
int p[maxn];
int sz[maxn];
int cnt = 0;
void clear(int n){
    rep(i,0,n+1)p[i]=i,sz[i]=1;
}
int root(int x){
   while(x!=p[x]){
       p[x]=p[p[x]];
       x=p[x];
   }
   return x;  
}
void merge(int x,int y){
    int p1=root(x);
    int p2=root(y);
    if(p1==p2)return;
    cnt--;
    if(sz[p1]>=sz[p2]){
        p[p2]=p1;
        sz[p1]+=sz[p2];
    }
    else{
        p[p1]=p2;
        sz[p2]+=sz[p1];
    }
}

int solve(){
 
	int n; cin >> n;

	vector<int>p(n+1),cost(n+1);

	rep(i,1,n+1){
	
		cin >> p[i];
	}

	rep(i,1,n+1){

		cin >> cost[i];
	}


	auto greedy = [&]() -> int{

		clear(n);
		cnt = n;
		for(int i = 1; i <= n; i++){
			merge(i,p[i]);
		}

		vector<pii>sorted_cost;

		for(int i = 1; i <= n; i++){
			sorted_cost.push_back({cost[i],i});
		}

		sort(sorted_cost.begin(),sorted_cost.end());

		vector<pair<int,pii>>edges;

		for(int i = 1; i < n; i++){
			int u = root(sorted_cost[i].y);
			int v = root(sorted_cost[i-1].y);
			int w = abs(sorted_cost[i].x - sorted_cost[i-1].x);
			if(u != v){
				edges.push_back({w,{u,v}});
			}
		}

		sort(edges.begin(),edges.end());

		int ans = 0;
		for(auto i:edges){
			if(root(i.y.x) != root(i.y.y)){
				merge(i.y.x,i.y.y);
				ans += i.x;
			}	
		}
		return ans;
	};
	cout << greedy() << endl;
 return 0;
}



signed main(){
    ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    //freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
    #ifdef SIEVE
    sieve();
    #endif
    #ifdef NCR
    init();
    #endif
    int t;cin>>t;
    while(t--)solve();
    return 0;
}
 
Tester's Code 1
/***************************************************

@author: vichitr
Compiled On: 27 Mar 2021

*****************************************************/
#include<bits/stdc++.h>
#define MAX 9223372036854775807
#define endl "\n"
#define ll long long
#define int long long
// #define double long double
#define pb push_back
#define pf pop_front
#define mp make_pair
#define ip pair<int, int>
#define F first
#define S second

#define loop(i,n) for(int i=0;i<n;i++)
#define loops(i,s,n) for(int i=s;i<=n;i++)
#define fast ios::sync_with_stdio(0); cin.tie(NULL); cout.tie(NULL)
using namespace std;

// #include <ext/pb_ds/assoc_container.hpp> // Common file
// #include <ext/pb_ds/tree_policy.hpp>     // Including tree_order_statistics_node_updat
// using namespace __gnu_pbds;
// typedef tree<ip, null_type, less_equal<ip>, rb_tree_tag, tree_order_statistics_node_update> ordered_set;

// order_of_key (k) : Number of items strictly smaller than k .
// find_by_order(k) : K-th element in a set (counting from zero).

const ll MOD = 1e9+7;
const ll SZ = 107;
const ll N = 1e6+7;
const ll M = 1e4+7;

ll pwr(ll x, ll y)
{
    ll r = 1LL;
    while(y)
    {
        if(y&1)
            r = (r * x) % MOD;
        y >>= 1;
        x = (x * x) % MOD;
    }
    return r;
}

int inv(int x)
{
    return pwr(x, MOD-2ll);
}

int parent[N], siz[N];

int find_set(int v) {
    if (v == parent[v])
        return v;
    return parent[v] = find_set(parent[v]);
}

void union_sets(int a, int b) {
    a = find_set(a);
    b = find_set(b);
    if (a != b) {
        if (siz[a] < siz[b])
            swap(a, b);
        parent[b] = a;
        siz[a] += siz[b];
    }
}

void solve(){
    int n; cin>>n;
    int p[n+1], c[n+1];
    loops(i, 1, n) cin>>p[i];
    loops(i, 1, n) cin>>c[i];

    for(int i=0;i<=n;i++){
        parent[i] = i;
        siz[i] = 1;
    }

    vector<pair<int, int>> v;

    loops(i, 1, n){
        union_sets(i, p[i]);
        v.pb({c[i], p[i]});
    }
    sort(v.begin(), v.end());
    
    vector<vector<int>> e;
    for(int i=1;i<n;i++){
        int x = v[i-1].second;
        int y = v[i].second;
        int w = abs(v[i].first - v[i-1].first);
        x = find_set(x);
        y = find_set(y);
        if(x != y)
            e.pb({w, x, y});
    }

    sort(e.begin(), e.end());
    int ans = 0;
    for(int i=0;i<e.size();i++){
        int w = e[i][0];
        int x = e[i][1];
        int y = e[i][2];
        if(find_set(x) != find_set(y)){
            ans += w;
            union_sets(x, y);
        }
    }
    cout << ans << '\n';
}

signed main()
{
    // fast;
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif
    
    
    int t=1;
    cin >> t;
    assert(t <= 1e5);
    for(int i=1;i<=t;i++)
    {
        // cout<<"Case #"<<i<<": ";
        solve();
        
    }
    return 0;
}

Tester's Code 2
#include <bits/stdc++.h> 
using namespace std;
vector<int> Arr;
int root(int i)
{
    while(Arr[ i ] != i)           
    {
        i = Arr[ i ];
    }
    return i;
}
void un(int A ,int B)
{
    int root_A = root(A);       
    int root_B = root(B);  
    Arr[ root_A ] = root_B ;      
}
bool find(int A,int B)
{
    if( root(A)==root(B) ) 
        return true;
    else
        return false;
}
void solve()
{
    int n;
    cin>>n;
    vector<int> p(n+1);
    vector<int> cost(n+1);
    vector<pair<int,int>> cp;
    Arr.clear();
    Arr.resize(n+1);
    for(int i=0;i<=n;++i)
        Arr[i]=i;
    for(int i=1;i<=n;++i)
    {
        cin>>p[i];
    }
    for(int i=1;i<=n;++i)
    {
        if(!find(i,p[i]))
            un(i,p[i]);
    }
    for(int i=1;i<=n;++i)
        cin>>cost[i]; 
    for(int i=1;i<=n;++i)
        cp.push_back({cost[i],p[i]});
    sort(cp.begin(),cp.end());
    vector<pair<int, pair<int,int> > > edges;
    for(int i=1;i<cp.size();++i)
    {
        int cost = abs(cp[i].first - cp[i-1].first);
        int u = cp[i].second;
        int v = cp[i-1].second;
        edges.push_back({cost,{u,v}});
    }
    sort(edges.begin(),edges.end());
    long long ans = 0;
    for(int i=0;i<edges.size();++i)
    {
        int cost = edges[i].first;
        int u = edges[i].second.first;
        int v = edges[i].second.second;
        if(!find(u,v))
        {
            ans+=cost;
            un(u,v);
        }
    }
    cout<<ans<<endl;
    
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        solve();
    }
}

If you have used any other approach, share your approach in comments!
If you have any doubts, ask them in comments!

2 Likes