题解 LOJ535【「LibreOJ Round #6」花火】/ ZR22NOIP9A【逆序对】/ SS241007B【逆序对】

posted on 2022-10-24 09:07:38 | under 题解 | source

problem

排列 \(a\),可以进行至多一次操作:交换 \(a\) 中的任意两个数。求操作后最少的逆序对数。\(n\leq 10^6\)

solution 0

相当于能去掉的最多的逆序对。

\[[*]=\max_{l,r}\{c(l,r,<a_{l-1})-c(l,r,>a_{l-1})+c(l,r,>a_r)-c(l,r,<a_r)\} \]

solution 1

思考:交换操作到底是什么?

答案可以为 \(0\)\(a_l<a_r\) 时答案为负数,弃之。画图理解:

\[[*]=2\times \max_{l,r,a_l>a_r}\{count(l,r,[a_r,a_l])\}-3. \]

(这是奇数!)

原题 \(\Rightarrow\) 平面上有形如 \((i,a_i)\)\(n\) 个点,其中 \(a_i\) 是一个排列,求一个子矩阵满足:

  • 左上角和右下角各有一个点;

最大化子矩阵内的点数。

对其暴力,\(O(n^2\log n)\),不过比 solution 0 优越的多。

solution 2

如果一个点 \((x_1,y_1)\) 作为左上角,同时有一个 \((x_2,y_2),s.t.,x_2<x_1,y_2>y_1\) 那么 \((x_1,y_1)\) 是不是就寄了?

那么真正有效的左上角一定满足 \(x_1<x_2,y_1<y_2\),右下角也是一样的。

接下来是惊人结论:随着左上角决策向上移,右下角的最优决策也随之上移!这就是决策单调性。

不能直接 \(O(n)\) 枚举(是假的呢)。考虑怎么用决策单调性。难道暴力吗?

solution 3.1

考虑这么一件事情:就是你已经有 \((x_1,y_1,x_2,y_2)\) 的答案,欲将扩展到 \((x_1,y_1,x_2{\color{red}+1},y_2)\)。这是 \(O(\log n)\) 的吗?

其实不然,观察到这是一个排列,在那个横坐标上就一个点,暴力就行。\(O(1)\)

考虑像 CF868F 那样进行分治。当前分治左上角为 \([l,r]\),右下角可用决策为 \([L,R]\)。试图计算 \(mid=\frac{l+r}{2}\) 的答案,直接扫一遍 \([L,R]\),找一个最优的决策点 \(P\),那么 \([l,mid-1]\)\([L,P]\)\([mid+1,r]\)\([P,R]\) 就能尽可能的利用信息。

转移的时候用莫队那样的东西做。分析复杂度:每一层每个指针都均摊 \(O(n)\),一共 \(O(n\log n)\) 的移动量。复杂度很对。

solution 3.2

考虑扫描线。

一个点对前缀最大值的贡献是一段区间;枚举右下角时可用决策具有单调性。

code

决策单调性分治
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m,k,a[1000010],b[1000010],c[1000010],d[1000010];
struct ds{
	int lx,rx,ly,ry,ans;
	ds():lx(1),rx(0),ly(1),ry(0),ans(0){}
	void addline(int x,int ly,int ry){ans+=ly<=a[x]&&a[x]<=ry;}
	void delline(int x,int ly,int ry){ans-=ly<=a[x]&&a[x]<=ry;}
	void addrow(int y,int lx,int rx){ans+=lx<=d[y]&&d[y]<=rx;}
	void delrow(int y,int lx,int rx){ans-=lx<=d[y]&&d[y]<=rx;}
	int operator()(int Lx,int Rx,int Ly,int Ry){
		while(Lx<lx) addline(--lx,ly,ry);
		while(rx<Rx) addline(++rx,ly,ry);
		while(Ly<ly) addrow(--ly,lx,rx);
		while(ry<Ry) addrow(++ry,lx,rx);
		while(lx<Lx) delline(lx++,ly,ry);
		while(rx>Rx) delline(rx--,ly,ry);
		while(ly<Ly) delrow(ly++,lx,rx);
		while(Ry<ry) delrow(ry--,lx,rx);
		return ans;
	}
} ccf;
int solve(int l,int r,int L,int R){
	if(l>r) return -1e9;
	int mid=(l+r)>>1,p=L,pans=-1e9;
	for(int i=L;i<=R;i++){
		int res=ccf(b[mid],c[i],a[c[i]],a[b[mid]]);
//		printf("ccf(%d,%d,%d,%d)=%d\n",b[mid],c[i],a[c[i]],a[b[mid]],res);
		if(b[mid]<c[i]&&a[b[mid]]>a[c[i]]&&res>pans) pans=res,p=i;
	}
//	printf("solve(%d,%d,%d,%d)=%d\n",l,r,L,R,pans);
	return max({pans,solve(l,mid-1,L,p),solve(mid+1,r,p,R)});
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),d[a[i]]=i;
	for(int i=1;i<=n;i++) if(!m||a[b[m]]<a[i]) b[++m]=i;
	for(int i=n;i>=1;i--) if(!k||a[c[k]]>a[i]) c[++k]=i;
	reverse(c+1,c+k+1);
	printf("%d\n",max(solve(1,m,1,k)*2-3,0));
	return 0;
}
线段树
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
template<int N> struct segtree{
	int ans[N<<2],tag[N<<2];
	segtree(){memset(ans,~0x3f,sizeof ans),memset(tag,0,sizeof tag);}
    //看,这里 ans 初值为 inf,贡献不能算到非决策点上,我们要 ban 掉
	void add(int p,int k){tag[p]+=k,ans[p]+=k;}
	void pushdown(int p){add(p<<1,tag[p]),add(p<<1|1,tag[p]),tag[p]=0;}
	void modify(int L,int R,int k,int p=1,int l=1,int r=N){
		if(L<=l&&r<=R) return add(p,k);
		int mid=(l+r)>>1; pushdown(p);
		if(L<=mid) modify(L,R,k,p<<1,l,mid);
		if(mid<R) modify(L,R,k,p<<1|1,mid+1,r);
		ans[p]=max(ans[p<<1],ans[p<<1|1]);
	}
	void cover(int x,int k,int p=1,int l=1,int r=N){
		if(l==r) return (void)(ans[p]=k,tag[p]=0);
		int mid=(l+r)>>1; pushdown(p);
		if(x<=mid) cover(x,k,p<<1,l,mid);
		else cover(x,k,p<<1|1,mid+1,r);
		ans[p]=max(ans[p<<1],ans[p<<1|1]);
	}
	int query(int L,int R,int p=1,int l=1,int r=N){
		if(L<=l&&r<=R) return ans[p];
		int mid=(l+r)>>1,res=-1e9; pushdown(p);
		if(L<=mid) res=max(res,query(L,R,p<<1,l,mid));
		if(mid<R) res=max(res,query(L,R,p<<1|1,mid+1,r));
		return res;
	}
};
int n,a[1000010],b[1000010],premax[1000010],sufmin[1000010];
segtree<1000010> t;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[a[i]]=i;
	premax[0]=-1e9;for(int i=1;i<=n;i++) premax[i]=max(premax[i-1],a[i]);
	sufmin[n+1]=1e9;for(int i=n;i>=1;i--) sufmin[i]=min(sufmin[i+1],a[i]);
	int last=1,now=1,ans=0;
	for(int i=1;i<=n;i++){
		if(premax[i]==a[i]) t.cover(i,0);//决策点可用,开启
		t.modify(lower_bound(premax+1,premax+i+1,a[i])-premax,i,1);
		if(sufmin[i]==a[i]){
			for(;last<=a[i];last++) t.modify(1,b[last],-1);
			for(;premax[now]<a[i];now++);
			if(now<=i) ans=max(ans,t.query(now,i));
		}
	}
	printf("%d\n",max(2*ans-1,0));
	return 0;
}
线段树
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) ((void)fprintf(stderr, ##__VA_ARGS__))
#else
#define endl "\n"
#define debug(...) ((void)0)
#endif
using LL = long long;
template <int N>
struct fenwick {/*{{{*/
  int c[N + 10];
  fenwick() { memset(c, 0, sizeof c); }
  void add(int p, int k) { for (; p <= N; p += p & -p) c[p] += k; }
  int qry(int p) { int r = 0; for (; p >= 1; p -= p & -p) r += c[p]; return r; }
};/*}}}*/
template <int N>
struct segtree {/*{{{*/
  int ans[N << 2], tag[N << 2];
  void maintain(int p) { ans[p] = max(ans[p << 1], ans[p << 1 | 1]); }
  void spread(int p, int k) { tag[p] += k, ans[p] += k; }
  void pushdown(int p) { if (tag[p]) spread(p << 1, tag[p]), spread(p << 1 | 1, tag[p]), tag[p] = 0; }
  void modify(int L, int R, int k, int p, int l, int r) {
    if (L <= l && r <= R) return spread(p, k);
    int mid = (l + r) >> 1;
    pushdown(p);
    if (L <= mid) modify(L, R, k, p << 1, l, mid);
    if (mid < R) modify(L, R, k, p << 1 | 1, mid + 1, r);
    maintain(p);
  }
  int query(int L, int R, int p, int l, int r) {
    if (L <= l && r <= R) return ans[p];
    int mid = (l + r) >> 1, ret = -1e9;
    pushdown(p);
    if (L <= mid) ret = max(ret, query(L, R, p << 1, l, mid));
    if (mid < R) ret = max(ret, query(L, R, p << 1 | 1, mid + 1, r));
    return ret;
  }
};/*}}}*/
int n, a[1000010];
int bruteforce() {/*{{{*/
  static int f[1010][1010];
  for (int i = 1; i <= n; i++) {
    int pre = 0;
    for (int j = i + 1; j <= n; j++) f[i][j] += pre, pre += (a[i] > a[j] ? -1 : +1);
    pre = 0;
    for (int j = i - 1; j >= 1; j--) f[i][j] += pre, pre += (a[j] > a[i] ? -1 : +1);
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    for (int j = i + 1; j <= n; j++) ans = min(ans, f[i][j] + f[j][i] + (a[i] > a[j] ? -1 : +1));
  }
  return ans;
}/*}}}*/
bool cmpi(int i, int j) { return i < j; }
bool cmpv(int i, int j) { return a[i] < a[j]; }
bool key[1000010];
vector<tuple<int, int, int>> opt[1000010];
segtree<1000010> seg;
int solve() {
  int ans = -1e9;
  vector<int> lhs, rhs;
  for (int i = 1; i <= n; i++) if (lhs.empty() || a[lhs.back()] < a[i]) lhs.push_back(i), key[i] = true;
  for (int i = n; i >= 1; i--) if (rhs.empty() || a[rhs.back()] > a[i]) rhs.push_back(i), key[i] = true;
  reverse(rhs.begin(), rhs.end());
  for (int i = 1; i <= n; i++) if (!key[i]) {
    int ld = lower_bound(rhs.begin(), rhs.end(), i, cmpi) - rhs.begin();
    int rd = upper_bound(rhs.begin(), rhs.end(), i, cmpv) - rhs.begin() - 1;
    int lu = lower_bound(lhs.begin(), lhs.end(), i, cmpv) - lhs.begin();
    int ru = upper_bound(lhs.begin(), lhs.end(), i, cmpi) - lhs.begin() - 1;
    if (ld <= rd && lu <= ru) opt[lu].emplace_back(ld, rd, +1), opt[ru + 1].emplace_back(ld, rd, -1);
  }
  int rsz = (int)rhs.size();
  for (int i = 0; i < (int)lhs.size(); i++) {
    for (auto&& e : opt[i]) seg.modify(get<0>(e), get<1>(e), get<2>(e), 1, 0, rsz - 1);
    int ld = upper_bound(rhs.begin(), rhs.end(), lhs[i], cmpi) - rhs.begin();
    int rd = lower_bound(rhs.begin(), rhs.end(), lhs[i], cmpv) - rhs.begin() - 1;
    if (ld <= rd) ans = max(ans, seg.query(ld, rd, 1, 0, rsz - 1));
  }
  return ans;
}
int main() {
#ifndef LOCAL
#ifndef NF
  freopen("inverse.in", "r", stdin);
  freopen("inverse.out", "w", stdout);
#endif
  cin.tie(nullptr)->sync_with_stdio(false);
#endif
  cin >> n;
  for (int i = 1; i <= n; i++) cin >> a[i];
//cout << -bruteforce() << endl;
  cout << max(0, solve() * 2 + 1) << endl;
  return 0;
}

posted @ 2022-11-14 21:56  caijianhong  阅读(57)  评论(0编辑  收藏  举报