[CCO 2023] Line Town

发布时间 2023-07-03 19:58:52作者: yyyyxh

场上有点降智……其实会了互不相同的 sub 就可以会第一个 sub 甚至正解的。

题意

给定一个序列 \(a_i\)\(|a_i|\leq 10^9\),每次可以选两个相邻元素 \(\texttt{swap}\),然后将两个元素同时取反。问最少操作几次可以使数列不降。

做法

场上做法考虑 \(|a_i|\) 互不相同的部分分。我们考虑枚举最终有几个负数几个正数,零随便算它跟正数还是跟负数一块。那么负数绝对值降序,正数绝对值升序。我们再考虑将原序列复合上一个什么样的置换 \(p\) 才可以与最终序列的绝对值对应上。我们发现一个数最后有没有被取反只跟这个 \(p\) 有关。具体地,一个数 \(a_i\) 如果在最终的序列是 \(-a_i\),当且仅当在 \(p\) 中包含 \(i\) 的逆序对数是奇数个。

包含 \(i\) 的逆序对数的奇偶性我们在 Jewel of Data Structure Problems 这道题中见过。结论就是它的值等于 \(i\oplus p_i\) 的奇偶性。

那么我们首先对于所有奇数位置的 \(a_i\) 取个反,然后只用考虑 \(p_i\) 的贡献了。

如果我们将新得到的 \(a_i\) 负数标成 \(1\),正数标成 \(0\),我们相当于按绝对值从小到大填数,从中间往两边填入一些 \(01\),使得最终串变成 \(\color{blue}{\dots 01010}\color{red}{01010 \dots}\) 或者 \(\color{blue}{\dots 10101}\color{red}{10101 \dots}\) 这种形式。我们需要最小化的是 \(p\) 的逆序对数。我们发现每加一个数逆序对的变化只跟加的那个数和加在左边还是右边有关。记录一些奇偶性 DP 即可。

#include <cstdio>
#include <algorithm>
using namespace std;
int read(){/* read */}
typedef long long ll;
const int N=1000003;
const ll INF=0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> pii;
int n,a[N];
pii b[N];
ll f[2][2][2],g[2][2][2];
void chmn(ll &x,ll v){if(x>v) x=v;}
int tr[N];
int qry(int x){
	int res=0;
	for(int i=x;i;i^=(i&-i)) res+=tr[i];
	return res;
}
void upd(int x){
	for(int i=x;i<=n;i+=(i&-i)) ++tr[i];
}
int main(){
	n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i) if(i&1) a[i]=-a[i];
	for(int i=1;i<=n;++i) b[i]=pii(abs(a[i]),i);
	sort(b+1,b+n+1);
	for(int t=0;t<2;++t)
		for(int x=0;x<2;++x)
			for(int y=0;y<2;++y) f[t][x][y]=INF;
	f[1][0][0]=f[0][1][1]=0;
	for(int i=1;i<=n;++i){
		int p=b[i].second;
		int pre=qry(p),suf=i-1-qry(p);
		upd(p);
		bool c=(a[p]<0);
		for(int t=0;t<2;++t)
			for(int x=0;x<2;++x)
				for(int y=0;y<2;++y)
					g[t][x][y]=f[t][x][y],f[t][x][y]=INF;
		for(int t=0;t<2;++t)
			for(int x=0;x<2;++x)
				for(int y=0;y<2;++y){
					if(g[t][x][y]==INF) continue;
					if(c==x) chmn(f[t^1][x^1][y],g[t][x][y]+pre);
					if(c==y) chmn(f[t][x][y^1],g[t][x][y]+suf);
				}
	}
	ll res=INF;
	for(int x=0;x<2;++x)
		for(int y=0;y<2;++y) chmn(res,f[0][x][y]);
	if(res==INF) puts("-1");
	else printf("%lld\n",res);
	return 0;
}

考虑有重复数时怎么做。我们每次将一整段绝对值相同的劈成两个部分,分别加在序列的左边和右边。注意到对于序列的 \(01\) 情况有些限制,我们将绝对值相同且 01 情况相同的数拿下来排序。对于限制一定是 \(0\) 的位一定是按原数列下标从左往右填入所有的 \(0\)\(1\) 同理,所以我们只需要考虑新加入的段中 \(01\) 之间的逆序对,和新段与原序列形成的逆序对。

我们枚举如何划分新段,那么 \(01\) 情况的限制要么不变,要么每次只交换一对 \(01\),所以逆序对容易动态维护。新段与原序列的逆序对也可以用树状数组简单维护。剩下的 DP 部分和 \(|a_i|\) 互不相同的做法一样。

时间复杂度 \(O(n\log n)\)。看代码感觉自己的做法比其它人要麻烦点。

#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
int read(){/* read */}
typedef long long ll;
const int N=1000003;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,a[N];
ll f[2][2][2],g[2][2][2];
void chmn(ll &x,ll v){if(x>v) x=v;}
struct bit{
	int tr[N],stk[N],tp;
	int qry(int x,bool t){
		int res=0;
		for(int i=x;i;i^=(i&-i)) res+=tr[i];
		if(t) return tp-res;
		return res;
	}
	void upd(int x){
		stk[++tp]=x;
		for(int i=x;i<=n;i+=(i&-i)) ++tr[i];
	}
	void clear(){
		while(tp){
			for(int i=stk[tp--];i<=n;i+=(i&-i))
				if(tr[i]) tr[i]=0;
				else break;
		}
	}
}F,G;
int buc[N],rk;
vector<int> v[N][2];
int seq[N];
int main(){
	n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i) if(i&1) a[i]=-a[i];
	for(int i=1;i<=n;++i) buc[++rk]=abs(a[i]);
	sort(buc+1,buc+rk+1);rk=unique(buc+1,buc+rk+1)-buc-1;
	for(int i=1;i<=n;++i)
		if(a[i]<0) v[lower_bound(buc+1,buc+rk+1,-a[i])-buc][1].emplace_back(i);
		else v[lower_bound(buc+1,buc+rk+1,a[i])-buc][0].emplace_back(i);
	for(int t=0;t<2;++t)
		for(int x=0;x<2;++x)
			for(int y=0;y<2;++y) f[t][x][y]=INF;
	if(buc[1]) f[1][0][0]=f[0][1][1]=0;
	else{
		int len=v[1][0].size();
		for(int x:v[1][0]) F.upd(x);
		for(int i=0;i<=len;++i){
			f[~i&1][i&1][(len^i)&1]=0;
			f[i&1][~i&1][~(len^i)&1]=0;
		}
	}
	for(int pos=1;pos<=rk;++pos){
		if(!buc[pos]) continue;
		int len=v[pos][0].size()+v[pos][1].size();
		for(int t=0;t<2;++t)
			for(int x=0;x<2;++x)
				for(int y=0;y<2;++y)
					g[t][x][y]=f[t][x][y],f[t][x][y]=INF;
		for(int t=0;t<2;++t)
			for(int x=0;x<2;++x)
				for(int y=0;y<2;++y){
					if(g[t][x][y]==INF) continue;
					if(x!=y){
						for(int tt=0;tt<2;++tt){
							int o[2]={0,0};
							ll cur=0;
							if(tt){
								if(v[pos][x].empty()) goto nxt;
								seq[0]=v[pos][x][o[x]++];
								cur+=F.qry(seq[0],0);
								G.upd(seq[0]);
							}
							for(int i=tt,par=y;i<len;++i,par^=1){
								if(o[par]>=int(v[pos][par].size())) goto nxt;
								seq[i]=v[pos][par][o[par]++];
								cur+=F.qry(seq[i],1)+G.qry(seq[i],1);
								G.upd(seq[i]);
							}
							if(o[0]<int(v[pos][0].size())||o[1]<int(v[pos][1].size())) goto nxt;
							chmn(f[t^tt][x^tt][y^(len&1)^tt],g[t][x][y]+cur);
							for(int i=tt;i+1<len;i+=2){
								cur-=F.qry(seq[i],1);
								cur-=F.qry(seq[i+1],1);
								cur+=F.qry(seq[i],0);
								cur+=F.qry(seq[i+1],0);
								chmn(f[t^tt][x^tt][y^(len&1)^tt],g[t][x][y]+cur);
							}
							nxt: G.clear();
						}
					}
					else{
						for(int tt=0;tt<2;++tt){
							int o[2]={0,0};
							ll cur=0;
							if(tt){
								if(v[pos][x].empty()) goto jump;
								seq[0]=v[pos][x][o[x]++];
								cur+=F.qry(seq[0],0);
								G.upd(seq[0]);
							}
							for(int i=tt,par=y;i<len;++i,par^=1){
								if(o[par]>=int(v[pos][par].size())) goto jump;
								seq[i]=v[pos][par][o[par]++];
								cur+=F.qry(seq[i],1)+G.qry(seq[i],1);
								G.upd(seq[i]);
							}
							if(o[0]<int(v[pos][0].size())||o[1]<int(v[pos][1].size())) goto jump;
							chmn(f[t^tt][x^tt][y^(len&1)^tt],g[t][x][y]+cur);
							for(int i=tt;i+1<len;i+=2){
								if(seq[i]>seq[i+1]) --cur;else ++cur;
								swap(seq[i],seq[i+1]);
								cur-=F.qry(seq[i],1);
								cur-=F.qry(seq[i+1],1);
								cur+=F.qry(seq[i],0);
								cur+=F.qry(seq[i+1],0);
								chmn(f[t^tt][x^tt][y^(len&1)^tt],g[t][x][y]+cur);
							}
							jump: G.clear();
						}
					}
				}
		for(int x:v[pos][0]) F.upd(x);
		for(int x:v[pos][1]) F.upd(x);
	}
	ll res=INF;
	for(int x=0;x<2;++x)
		for(int y=0;y<2;++y) chmn(res,f[0][x][y]);
	if(res==INF) puts("-1");
	else printf("%lld\n",res);
	return 0;
}