给定 ([n] imes 3) 的两个排列 (A,B),每次操作可以选择 (xin[1,n]),将 (A) 的三个 (x) 中间那个移到开头/结尾。
求将 (A) 变为 (B) 的最小操作次数。(nle 33)。
我们设 (A) 中最后一次移动是 push_front 的元素记作 (L),是 push_back 的元素记作 (R),没有动过的元素记作 (M)。则最终 (A) 的状态形如 (Lcdots LMcdots MRcdots R)。(O(n^2)) 枚举分界点就可以确定每个元素被移动的情况。
对于每个数 (xin[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
", ~ans ? 3*n-ans : -1);
}
从下午开始写,调到了晚上 7 点,人都傻了(坑:
- 要分清楚两个数组(
- 不能想当然地写 if-else
就这样还能过 3/4 的点?