CF1672I PermutationForces

一个观察是这个题可以直接贪心选最小。证明考虑删除一个位置后,如果一个 \(|i-p_i|\) 变大,那么新的 \(|i-p_i|\) 也不会超过刚刚操作的代价。

考虑把排列看成上下各 \(1\sim n\) 的二分图,上面的第 \(i\) 个点向下面 \(p_i\) 连边。操作不会改变相对位置,所以考虑一直对于整个图操作,对于每组匹配维护当前代价 \(v_i\)。每次操作实际上就是把与选中线段相交的匹配的 \(v-1\)

\(i\le p_i\) 的为 \(1\) 类匹配,\(i>p_i\)\(2\) 类匹配,令它们代表的区间就是 \([\min(i,p_i),\max(i,p_i)]\)

一个观察是,对于两组同类匹配的区间 \([x_1,y_1], [x_2,y_2]\),如果 \(x_1<x_2<y_2<y_1\),那么我们肯定是先操作 \([x_2,y_2]\) 再操作 \([x_1,y_1]\)

同时我们可以注意到,如果我们只保留那些不存在 \([x_2,y_2]\) 的区间 \([x_1,y_1]\),那么整个图也是很有规律的。具体地,两类线段分别不相交,而且对于每一类中的线段,另一类中和它有交的线段形成一个区间,而这是很好做的。

考虑动态维护这些极小的区间,每次我们删除了一个区间后就尝试再加入同类的区间,在加入一个区间时再计算之前的删除的贡献。

注意算这些贡献时可能出现动态二维偏序的形式,但是我们都可以做一些处理。

考虑同类贡献时,因为这道题过程的特殊性保证了不会有包含当前区间的区间被删除,所以不难动态维护。

考虑异类贡献时,我们可以直接从刚刚被删的区间继承,然后新增的部分是只和 \(l\)\(r\) 有关的式子,然后就可以维护了。

总复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int> ;
template <typename T> void Chkmin(T &x, T y) { x = min(x, y); }
template <typename T> void Chkmax(T &x, T y) { x = max(x, y); }

const int inf = 1e9;
const int kN = 5e5 + 5, kS = kN * 4;
int n;
int p[kN];
int len[2];
pii seg[2][kN];
set<pii> stl[2], str[2];
bool used[2][kN], del[2][kN];

struct BIT {
  int tr[kN];
  void Update(int x, int v) {
    for(; x <= n; x += (x & -x)) tr[x] += v;
  }
  int Query(int x) {
    int ans = 0;
    for(; x; x &= (x - 1)) ans += tr[x];
    return ans;
  }
  void Set(int x, int v) {
    int d = v - Query(x);
    Update(x, d);
    Update(x + 1, -d);
  }
};
BIT bitl[2], bitr[2], bito[2];

#define ls (o << 1)
#define rs (o << 1 | 1)
struct SGT1 {
  pii mn[kS];
  SGT1() { fill(mn, mn + kS, pii {inf, inf}); }
  void Up(int o) { mn[o] = min(mn[ls], mn[rs]); }
  void Modify(int o, int l, int r, int x, pii v) {
    if(l == r) return void(mn[o] = v);
    int mid = (l + r) >> 1;
    if(mid < x) Modify(rs, mid + 1, r, x, v);
    else Modify(ls, l, mid, x, v);
    Up(o);
  }
  pii Query(int o, int l, int r, int x, int y) {
    if((l > y) || (r < x)) return pii {inf, inf};
    if((l >= x) && (r <= y)) return mn[o];
    int mid = (l + r) >> 1;
    return min(Query(ls, l, mid, x, y), Query(rs, mid + 1, r, x, y));
  }
};
SGT1 sgt1[2];

struct SGT2 {
  pii mn[kS];
  int tag[kS];
  SGT2() { fill(mn, mn + kS, pii {inf, inf}); }
  void Up(int o) { mn[o] = min(mn[ls], mn[rs]); }
  void Adt(int o, int t) { mn[o].first += t, tag[o] += t; }
  void Dn(int o) { if(int &t = tag[o]) Adt(ls, t), Adt(rs, t), t = 0; }
  void Update(int o, int l, int r, int x, int y, int v) {
    if((l > y) || (r < x)) return ;
    if((l >= x) && (r <= y)) return Adt(o, v);
    Dn(o);
    int mid = (l + r) >> 1;
    Update(ls, l, mid, x, y, v);
    Update(rs, mid + 1, r, x, y, v);
    Up(o);
  }
  void Modify(int o, int l, int r, int x, pii v) {
    if(l == r) return void(mn[o] = v);
    Dn(o);
    int mid = (l + r) >> 1;
    if(mid < x) Modify(rs, mid + 1, r, x, v);
    else Modify(ls, l, mid, x, v);
    Up(o);
  }
};
SGT2 sgt2[2];

void AddSeg(int typ, int id, int from) {
  int cl, cr, fl, fr;
  tie(cl, cr) = seg[typ][id];
  tie(fl, fr) = seg[typ][from];
  sgt1[typ].Modify(1, 1, n, cl, pii {inf, inf});
  stl[typ].emplace(cl, id);
  str[typ].emplace(cr, id);
  used[typ][id] = 1;
  int v = cr - cl;
  v -= bitl[typ].Query(n) - bitl[typ].Query(cl);
  v += bitr[typ].Query(n) - bitr[typ].Query(cr);
  if(from) {
    int op = bito[typ].Query(from);
    int rvt = 1 - typ;
    op += bitr[rvt].Query(fl) - bitr[rvt].Query(cl);
    op += bitl[rvt].Query(cr) - bitl[rvt].Query(fr);
    bito[typ].Set(id, op);
    v -= op;
  }
  sgt2[typ].Modify(1, 1, n, id, pii {v, id});
}

void TryAdd(int typ, int L, int R, int from) {
  int lst = L - 1;
  while(lst < R) {
    int r, id;
    tie(r, id) = sgt1[typ].Query(1, 1, n, lst + 1, R);
    if(r > R) return ;
    lst = seg[typ][id].first;
    AddSeg(typ, id, from);
  }
}

tuple<int, int, int> GetMin() {
  int tp, id, mn = inf;
  for(int typ : {0, 1}) {
    int v, p;
    tie(v, p) = sgt2[typ].mn[1];
    if(mn > v) mn = v, tp = typ, id = p;
  }
  return make_tuple(tp, id, mn);
}

void DelSeg(int typ, int id) {
  int cl, cr;
  tie(cl, cr) = seg[typ][id];
  sgt2[typ].Modify(1, 1, n, id, pii {inf, inf});
  stl[typ].erase(pii {cl, id});
  str[typ].erase(pii {cr, id});
  del[typ][id] = 1;
  bitl[typ].Update(cl, 1);
  bitr[typ].Update(cr, 1);
  int rvt = 1 - typ;
  auto itl = stl[rvt].lower_bound(pii {cr, -1});
  auto itr = str[rvt].lower_bound(pii {cl, -1});
  if((itl == stl[rvt].begin()) || (itr == str[rvt].end())) return ;
  int idl = itr -> second;
  int idr = prev(itl) -> second;
  bito[rvt].Update(idl, 1);
  bito[rvt].Update(idr + 1, -1);
  sgt2[rvt].Update(1, 1, n, idl, idr, -1);
}

int main() {
  // freopen("1.in", "r", stdin);
  // freopen("1.out", "w", stdout);
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n;
  for(int i = 1; i <= n; i++) {
    cin >> p[i];
    int l = min(i, p[i]), r = max(i, p[i]);
    bool typ = (i > p[i]);
    len[typ]++;
    seg[typ][len[typ]] = pii {l, r};
  }
  for(int typ : {0, 1}) {
    sort(seg[typ] + 1, seg[typ] + len[typ] + 1,
      [&](pii x, pii y) { return x.second < y.second; });
    for(int i = 1; i <= len[typ]; i++) {
      int l, r;
      tie(l, r) = seg[typ][i];
      sgt1[typ].Modify(1, 1, n, l, pii {r, i});
    }
  }
  TryAdd(0, 1, n, 0);
  TryAdd(1, 1, n, 0);
  int ans = 0;
  for(int t = 1; t <= n; t++) {
    int typ, id, cost;
    tie(typ, id, cost) = GetMin();
    Chkmax(ans, cost);
    DelSeg(typ, id);
    int liml = 1, limr = n;
    auto it = stl[typ].lower_bound(pii {seg[typ][id].first, -1});
    if(it != stl[typ].end()) limr = seg[typ][it -> second].second;
    if(it != stl[typ].begin()) liml = seg[typ][prev(it) -> second].first;
    TryAdd(typ, liml, limr, id);
  }
  cout << ans << "\n";
  return 0;
}
posted @ 2025-08-23 11:42  CJzdc  阅读(13)  评论(0)    收藏  举报