• LOJ2818 「eJOI2018」循环排序


    LOJ2818 「eJOI2018」循环排序

    题目链接

    本题题解

    约定:以下我们定义无向图的“连通块”,是指它的极大基图连通子图。其中基图是指把有向边边视作无向边,得到的无向图。

    首先要搞清楚,对于一种排序方案,有两个参数来衡量它:操作次数,和操作到的位置数。本题里,我们要在满足【操作到的位置数】(leq s) 的前提下,最小化【操作次数】。思考时,千万不要将这两个量搞混了。

    先将 (a) 序列离散化。设最大值为 (m)。然后求出排好序的序列长什么样子,设为 (b)

    建一张 (m) 个点的图。对每个 (i) ((1leq ileq n)),若 (a_i eq b_i),我们连一条有向边 ((b_i,a_i)),原因稍后解释。记这条边的“编号”为 (i)

    容易发现,每个 (a_i eq b_i)(i) 至少要作为【操作到的位置】出现一次。设这样的 (i)(k) 个,则【操作到的位置数】至少为 (k)。所以如果 (k > s),则可以直接判为无解。否则,发现【操作到的位置数】是可以等于 (k) 的。考虑我们刚刚建出来的有向图,这张图里每个点入度一定等于出度。因此这张图里的每个连通块,都存在欧拉回路。可以构造出一种操作方案是:对每个连通块,求出它的欧拉回路,按顺序输出所有边。此时读者不难发现,根据我们的建图方式,把一个连通块的欧拉回路上每条边的编号按顺序操作一遍后,就能使连通块里所有数值走到它最终应该走到的位置。

    特别地,如果 (a) 是一个排列,则我们建出的有向图实际就是若干个环。每个点入度、出度都为 (1)。这是入度等于出度的一种特殊情况。应该可以帮助读者更好地理解上述操作策略。

    设有 (c) 个连通块。那么在上述的操作策略中,我们所用的【操作次数】为 (c),【操作到的位置数】为 (k)。前面已经说明过,这是能将【操作到的位置数】最小化的方案。但如果在允许的范围内((leq s)),把【操作到的位置数】扩大一些,可能能进一步减小【操作次数】。

    具体来说,我们可以用 (1) 次操作,将若干个连通块合并为一个连通块,使新连通块仍然存在欧拉回路。具体的方法是在每个连通块里任选一条边,设选出的边编号分别为 (i_1,i_2,dots,i_t),那么对 (i_1,i_2,dots ,i_t) 做一次操作,就能使这些连通块以一个环的形式串起来。然后我们只需要再对新连通块做 (1) 次操作,就能使里面的数全部走到最终位置!

    换句话说,无论多少个连通块,我们只需要 (2) 次操作,就能将它们全部排好序。但同时,这会使得【操作到的位置数】增大相应的连通块数量。所以有可能我们不能对所有连通块进行操作。具体来说,我们只会任选 (x = min(c, s - k)) 个连通块,将它们合并。最终,若 (x < 2),则【操作次数】就是 (c)(不搞合并了),否则是:(2 + (c - x))

    还有一个小问题,就是求有向图的欧拉回路。我用 ( ext{dfs}) 来实现。那么对于有向边 ((u,v)),只能在从 (v) 回溯时输出(或记录)这条边,而不能在进入 (v) 之前输出,否则输出的可能就不是一条连续、完整的路径了。但是这样记录下的路径,与我们实际走的顺序其实是反的。所以你可以把路径 ( ext{reverse}) 一下,或者在建边的时候就干脆建反向边。

    时间复杂度 (O(nlog n))。因为要排序。

    参考代码

    在 LOJ 查看

    // problem: LOJ2818
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    
    template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
    template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
    
    const int MAXN = 2e5;
    int n, lim, a[MAXN + 5], goal[MAXN + 5];
    int vals[MAXN + 5], cnt_val;
    int need_oper_pos;
    vector<vector<int> > ans;
    
    int fa[MAXN + 5], sz[MAXN + 5];
    int last_edge[MAXN + 5];
    int get_fa(int u) { return (u == fa[u]) ? u : (fa[u] = get_fa(fa[u])); }
    void unite(int u, int v, int e) {
    	int fu = get_fa(u);
    	int fv = get_fa(v);
    	if (fu != fv) {
    		if (sz[fu] > sz[fv])
    			swap(fu, fv);
    		fa[fu] = fv;
    		sz[fv] += sz[fu];
    	}
    	last_edge[fu] = e;
    }
    
    struct EDGE { int nxt, to, eid; } edge[MAXN + 5];
    int head[MAXN + 5], tot;
    inline void add_edge(int u, int v, int eid) {
    	edge[++tot].nxt = head[u];
    	edge[tot].to = v;
    	edge[tot].eid = eid;
    	head[u] = tot;
    }
    vector<int> pos;
    void dfs(int u) {
    	for (int& i = head[u]; i; ) {
    		int v = edge[i].to;
    		int eid = edge[i].eid;
    		i = edge[i].nxt;
    		dfs(v);
    		pos.pb(eid);
    	}
    }
    int main() {
    	cin >> n >> lim;
    	for (int i = 1; i <= n; ++i) {
    		cin >> a[i];
    		vals[++cnt_val] = a[i];
    	}
    	sort(vals + 1, vals + cnt_val + 1);
    	for (int i = 1; i <= n; ++i) goal[i] = vals[i]; // 目标序列
    	cnt_val = unique(vals + 1, vals + cnt_val + 1) - (vals + 1);
    	
    	for (int i = 1; i <= cnt_val; ++i) {
    		fa[i] = i;
    		sz[i] = 1;
    	}
    	for (int i = 1; i <= n; ++i) {
    		a[i] = lob(vals + 1, vals + cnt_val + 1, a[i]) - vals;
    		goal[i] = lob(vals + 1, vals + cnt_val + 1, goal[i]) - vals;
    		// cerr << a[i] << " 
    "[i == n];
    		if (a[i] != goal[i]) {
    			unite(a[i], goal[i], i);
    			need_oper_pos++;
    		}
    	}
    	if (need_oper_pos > lim) {
    		cout << -1 << endl;
    		return 0;
    	}
    	int rest = lim - need_oper_pos;
    	// cerr << rest << endl;
    	if (rest >= 2) {
    		for (int i = 1; i <= cnt_val; ++i) {
    			if (get_fa(i) == i && last_edge[i] != 0) {
    				pos.pb(last_edge[i]);
    				if (SZ(pos) == rest)
    					break;
    			}
    		}
    		if (SZ(pos) >= 2) {
    			ans.pb(pos);
    			int last_val = a[pos.back()];
    			for (int i = SZ(pos) - 1; i >= 1; --i) {
    				a[pos[i]] = a[pos[i - 1]];
    			}
    			a[pos[0]] = last_val;
    		}
    	}
    	for (int i = 1; i <= n; ++i) {
    		if (a[i] != goal[i]) {
    			// cerr << i << " " << a[i] << " " << goal[i] << endl;
    			add_edge(a[i], goal[i], i);
    		}
    	}
    	for (int i = 1; i <= n; ++i) {
    		if (head[i]) {
    			vector<int>().swap(pos);
    			dfs(i);
    			ans.pb(pos);
    		}
    	}
    	cout << SZ(ans) << endl;
    	for (int i = 0; i < SZ(ans); ++i) {
    		cout << SZ(ans[i]) << endl;
    		for (int j = 0; j < SZ(ans[i]); ++j) {
    			cout << ans[i][j] << " 
    "[j == SZ(ans[i]) - 1];
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    Web 组件是什么
    amazeui学习笔记二(进阶开发2)--Web组件简介Web Component
    .less为后缀的文件是什么
    amazeui学习笔记二(进阶开发1)--项目结构structure
    html中的瀑布流是什么
    HTML5 API 是什么
    epoll使用具体解释(精髓)
    DataTable.AcceptChanges方法有何用处
    cer, pfx 创建,而且读取公钥/密钥,加解密 (C#程序实现)
    超赞的.NET办公软件库
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14054777.html
Copyright © 2020-2023  润新知