CF1685D2 Permutation Weight 【构造,贪心】

为了方便,规定下标在循环意义下。

给定长为 \(n\) 的排列 \(p\),求长为 \(n\) 的排列 \(q\) 使得 \(\sum_{i=1}^n|q_i-p_{q_{i+1}}|\) 最小,若相同则求字典序最小的。

\(2\le n\le 200\)\(\sum n\le 400\)


考虑 \(q_i\to p_{q_{i+1}}\) 形成一堆环,一个环 \((b_1,\cdots,b_m)\) 贡献 \(\sum_{i=1}^m|b_i-b_{i+1}|\)。当 \(b_i\) 是权值连续的单峰数列时取到最小值 \(2(m-1)\)

考虑 \(p\) 的每个环,将每个环缩成一个点,而 \(q_i\)\(p_{q_i}\) 在同个环里,所以形成一个大环,从而要求整个图连通,考虑其生成树,每个长为 \(m\) 的环只能选 \(m-1\) 条边,从而 \(\sum(m-1)\ge k-1\),即答案 \(\ge 2(k-1)\)

构造说明可以取到等号:顺序枚举 \(x\in[1,n)\),若 \(x\)\(x+1\) 不在同一个环,就用 \(q_i\to p_{q_{i+1}}\) 连成一个环,这就做完了 Easy Version,复杂度 \(\mathcal O(n\cdot\alpha(n))\)

#include<bits/stdc++.h>
using namespace std;
const int N = 202;
int T, n, q[N], fa[N];
int getf(int x){return x == fa[x] ? x : fa[x] = getf(fa[x]);}
void comb(int x, int y){x = getf(x); y = getf(y); if(x != y) fa[x] = y;}
void solve(){
	cin >> n;
	for(int i = 1;i <= n;++ i) fa[i] = i;
	for(int i = 1, x;i <= n;++ i){cin >> x; q[x] = i; comb(i, x);}
	for(int i = 1;i < n;++ i) if(getf(i) != getf(i + 1)){swap(q[i], q[i + 1]); comb(i, i + 1);}
	for(int i = 1, x = 1;i <= n;++ i, x = q[x]) printf("%d%c", x, " \n"[i == n]);
}
int main(){
	ios::sync_with_stdio(0);
	cin >> T; while(T --) solve();
}

现在看看 Hard Version 咋搞,事实证明上述条件也是充分的,然后就按位贪心判断:首先 \(q_1=1\),问题转化为判断前缀 \(q_1,\cdots,q_l\)\(l-1\) 条边 \(q_i\to p_{q_{i+1}}\) 能否满足要求,这就有些复杂了,需要仔细审视条件:

  • 首先是单峰的条件:所有 \(q_i<p_{q_{i+1}}\) 的区间 \((q_i,p_{q_{i+1}})\) 不相交,所有 \(q_i>p_{q_{i+1}}\) 的区间 \((p_{q_{i+1}},q_i)\) 不相交;
  • 然后是权值连续的条件:对于 \(x\in[1,n)\),若 \((x,x+1)\) 被两个区间包含,则要求 \(x\) 是区间端点。
  • 然后是生成树的条件,首先是无环的要求:对于 \(x\in[1,n)\),若 \((x,x+1)\) 被某个区间包含,则对 \(x\)\(x+1\) 的所在环连边,要求无环;
  • 最后是连通的要求:对于 \(x\in[1,n)\),若不存在 \(q_i=p_{q_{i+1}}=x\)\(x+1\),且 \(x\) 是至多一个区间的右端点,且 \(x+1\) 是至多是一个区间的左端点,则对 \(x\)\(x+1\) 的所在环连边,要求连通。

直接模拟即可,时间复杂度 \(\mathcal O(n^3\cdot\alpha(n))\)

#include<bits/stdc++.h>
using namespace std;
const int N = 202;
int n, k, p[N], fa[N], id[N], ans[N];
bool vis[N];
int getf(int x){return x == fa[x] ? x : fa[x] = getf(fa[x]);}
int comb(int x, int y){x = getf(x); y = getf(y); if(x != y){fa[x] = y; return 0;} return 1;}
bool v[3][N];
int cnt[2][N];
bool chk(int l){
	for(int i = 0;i < 3;++ i) memset(v[i], 0, n + 1);
	for(int i = 0;i < 2;++ i) memset(cnt[i], 0, (n + 1) << 2);
	for(int i = 1;i < l;++ i){
		int x = ans[i], y = p[ans[i + 1]];
		if(x == y){v[2][x] = 1; continue;}
		bool f = x > y; if(f) swap(x, y);
		++ cnt[0][x]; ++ cnt[1][y];
		for(int j = x;j < y;++ j){
			if(v[f][j]) return 0;
			v[f][j] = 1;
		}
	}
	for(int i = 1;i < n;++ i) if(v[0][i] && v[1][i] && !cnt[0][i] && !cnt[1][i]) return 0;
	iota(fa, fa + k + 1, 0);
	for(int i = 1;i < n;++ i) if((v[0][i] || v[1][i]) && comb(id[i], id[i + 1])) return 0;
	iota(fa, fa + k + 1, 0);
	for(int i = 1;i < n;++ i) if(!v[2][i] && !v[2][i + 1] && cnt[0][i + 1] < 2 && cnt[1][i] < 2) comb(id[i], id[i + 1]);
	for(int i = 2;i <= k;++ i) if(getf(1) != getf(i)) return 0;
	return 1;
}
void solve(){
	cin >> n; k = 0;
	memset(vis, 0, n + 1);
	memset(id, 0, (n + 1) << 2);
	for(int i = 1;i <= n;++ i) cin >> p[i];
	for(int i = 1;i <= n;++ i) if(!id[i]){
		id[i] = ++ k;
		for(int j = p[i];j != i;j = p[j]) id[j] = k;
	}
	ans[1] = 1; putchar('1');
	for(int i = 2;i < n;++ i){
		for(ans[i] = 2;ans[i] <= n;++ ans[i]) if(!vis[ans[i]] && chk(i)) break;
		vis[ans[i]] = 1; printf(" %d", ans[i]);
	}
	for(ans[n] = 2;ans[n] <= n;++ ans[n]) if(!vis[ans[n]]) break;
	printf(" %d\n", ans[n]);
}
int main(){
	ios::sync_with_stdio(0);
	int T; cin >> T; while(T --) solve();
}
posted @ 2022-06-07 13:44  mizu164  阅读(144)  评论(0编辑  收藏  举报