ACL Contest 1 F Center Rearranging【分析性质、2-SAT】

传送门

给定 \([n]\times 3\) 的两个排列 \(A,B\),每次操作可以选择 \(x\in[1,n]\),将 \(A\) 的三个 \(x\) 中间那个移到开头/结尾。

求将 \(A\) 变为 \(B\) 的最小操作次数。\(n\le 33\)


我们设 \(A\) 中最后一次移动是 push_front 的元素记作 \(L\),是 push_back 的元素记作 \(R\),没有动过的元素记作 \(M\)。则最终 \(A\) 的状态形如 \(L\cdots LM\cdots MR\cdots R\)\(O(n^2)\) 枚举分界点就可以确定每个元素被移动的情况。

对于每个数 \(x\in[1,n]\),考虑 \(B\)\(x\) 的状态对应回 \(A\)\(x\) 的位置,则有

我们知道除了 LMR 这种情况之外,都可以确定 \(A\) 中与其对应的 M

先找到已知对应的 M,我们知道它们的顺序不变,否则无解。

假设现在已经确定了 LMR 中对应的 M,考虑如何构造操作:显然 Other 中的元素操作顺序无关,Group 1 的元素第三个要比第一个先动,Group 2 中的元素第一个要比第三个先动,然后最终序列中右边的 \(L\) 比左边的 \(L\) 先动,左边的 \(R\) 比右边的 \(R\) 先动。

这是一个必要条件,感性理解一下它是充分的(

如果把确定先动的向后动的连边,求一个拓扑序即为可能的操作序列。

然而拓扑排序很难做,我们考虑直接判环。由于这个图长得很好看,它如果有环那么一定有环是形如某个 Group 1 的第 1 和 3 个位置都比某个 Group 2 的第 1 和 3 个位置靠后,形成一个 \(4\) 个点的环。

LMR 的两种情况作为变量取值,转化为 2-SAT 判可行。总时间复杂度 \(O(n^4)\)

#include<bits/stdc++.h>
#define PB emplace_back
#define fi first
#define se second
using namespace std;
typedef pair<int, int> pii;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0;
	for(;ch < '0' || ch > '9';ch = getchar());
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
} template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int cnt, head[70], to[2500], nxt[2500], col[70], cnum, stk[70], tp, dfn[70], low[70], tim;
bool ins[70];
void add(int u, int v){to[++cnt] = v; nxt[cnt] = head[u]; head[u] = cnt;}
void dfs(int x){
	dfn[x] = low[x] = ++tim; stk[++tp] = x; ins[x] = true;
	for(int i = head[x];i;i = nxt[i])
		if(!dfn[to[i]]){
			dfs(to[i]); chmin(low[x], low[to[i]]);
		} else if(ins[to[i]]) chmin(low[x], dfn[to[i]]);
	if(low[x] == dfn[x]){ ++ cnum;
		do {
			col[stk[tp]] = cnum;
			ins[stk[tp]] = false;
		} while(stk[tp--] != x);
	}
}
int n, op[34], ans = -1;
vector<int> pa[34], pb[34];
vector<pii> mat;
bool calc(int L, int R){
	memset(head, 0, sizeof head); memset(op, 0, sizeof op); mat.resize(0);
	cnt = cnum = tp = tim = 0; memset(dfn, 0, sizeof dfn); memset(col, 0, sizeof col);
	auto get = [&](int x) -> string {return x <= L ? "L" : (x >= R ? "R" : "M");};
	auto check = [&](int a, int b) -> bool {return pb[a][0] >= pb[b][0] && pb[a][2] >= pb[b][2];};
	for(int i = 1;i <= n;++ i){
		string str = get(pb[i][0]) + get(pb[i][1]) + get(pb[i][2]);
		if(str == "LLL" || str == "RRR") return false;
		if(str == "LLR") op[i] = 1;
		else if(str == "LRR") op[i] = 2;
		else if(str == "LMR") op[i] = 3;
		else if(str == "MMR"){mat.PB(pa[i][0], pb[i][0]); mat.PB(pa[i][2], pb[i][1]);}
		else if(str == "LMM"){mat.PB(pa[i][0], pb[i][1]); mat.PB(pa[i][2], pb[i][2]);}
		else if(str == "MMM") for(int j = 0;j < 3;++ j) mat.PB(pa[i][j], pb[i][j]);
		else if(str == "MRR") mat.PB(pa[i][0], pb[i][0]);
		else if(str == "LLM") mat.PB(pa[i][2], pb[i][2]);
	} sort(mat.begin(), mat.end());
	for(int i = 1;i < mat.size();++ i) if(mat[i].se < mat[i-1].se) return false;
	for(int i = 1;i <= n;++ i) if(op[i] == 1)
		for(int j = 1;j <= n;++ j) if(op[j] == 2 && check(i, j)) return false;
	for(int i = 1;i <= n;++ i) if(op[i] == 3){
		int fbd = 0;
		for(pii &o : mat){
			bool flg = o.se < pb[i][1];
			if((o.fi < pa[i][0]) != flg) fbd |= 1;
			if((o.fi < pa[i][2]) != flg) fbd |= 2;
		} for(int j = 1;j <= n;++ j)
			if(op[j] == 1){if(check(j, i)) fbd |= 2;}
			else if(op[j] == 2){if(check(i, j)) fbd |= 1;}
			else if(j > i && op[j] == 3){
				bool flg = pb[i][1] < pb[j][1];
				if(check(i, j) || (pa[i][0] < pa[j][2]) != flg){
					add(i, j); add(j+n, i+n);
				} if(check(j, i) || (pa[i][2] < pa[j][0]) != flg){
					add(i+n, j+n); add(j, i);
				} if((pa[i][0] < pa[j][0]) != flg){
					add(i, j+n); add(j, i+n);
				} if((pa[i][2] < pa[j][2]) != flg){
					add(i+n, j); add(j+n, i);
				}
			}
		if(fbd == 3) return false;
		if(fbd == 1) add(i, i+n); else if(fbd == 2) add(i+n, i);
	} for(int i = 1;i <= n;++ i) if(op[i] == 3){
		if(!dfn[i]) dfs(i); if(!dfn[i+n]) dfs(i+n);
		if(col[i] == col[i+n]) return false;
	} return true;
}
int main(){ read(n);
	for(int i = 1, x;i <= 3*n;++ i){read(x); pa[x].PB(i);}
	for(int i = 1, x;i <= 3*n;++ i){read(x); pb[x].PB(i);}
	for(int l = 0;l <= 3*n;++ l)
		for(int r = l+1;r <= 3*n+1;++ r)
			if(r-l-1 > ans && calc(l, r)) ans = r-l-1;
	printf("%d\n", ~ans ? 3*n-ans : -1);
}

从下午开始写,调到了晚上 7 点,人都傻了(坑:

  1. 要分清楚两个数组(
  2. 不能想当然地写 if-else就这样还能过 3/4 的点?
posted @ 2021-04-02 19:01  mizu164  阅读(134)  评论(0编辑  收藏  举报