【JZOJ 省选模拟】pm

题目

Description
有一个 {1,2···n} 的排列 {a1 ,a2 ···an },你需要把它变成 {1,2···n}。
你可以进行两个阶段的操作。第一阶段中,你可以重复若干次,每次任选两个相邻元素并进行交换。
第二阶段中,你可以重复若干次,每次修改一个位置上的元素。第二阶段中,你可以任意修改元素,即使中间过程中序列不再是排列仍然可行。
你需要最小化两个阶段操作的总次数,并给出一种总操作次数最小的合法方案。为了简单起见,你只需要输出第一阶段的操作,SPJ 会自动计算第二阶段的最优操作,并将操作总次数和最优总次数比较。

Input
第一行一个正整数 n。
第二行 n 个正整数 a1 ,a2 ···an 。

Output

Sample Input
4
4 3 2 1

Sample Output
1
2
第一阶段交换了中间的两个元素,第二阶段修改第一和第四个元素,总共需要 3 次操作。可以证明这是最优的。

Data Constraint
Subtask 1(10pts):n ≤ 5。
Subtask 2(10pts):n ≤ 9。
Subtask 3(10pts):n ≤ 50。
Subtask 4(20pts):n ≤ 500。
Subtask 5(10pts):n ≤ 2000。
Subtask 6(20pts):n ≤ 10^5 。
Subtask 7(20pts):n ≤ 2 × 10^5 。

思路

假设我们把被交换过的元素连边,那么最后一定形成了若干个段。
对于一个长度为l的段,用操作2一定可以用l步完成,由于块是连通的,所以操作数一定>=l-1
现在问题变成了,我们需要在原序列中找出若干不相交的段,每个段的长度等于这个段中的逆序对个数加一,并且每个段占用的下标和其中包含的元素集合相同,每个这样的段都能省一操作。
考虑对于每个段右端点(如果有的话)找到最短的每个段占用的下标和其中包含的元素集合相同的段,容易发现只有这样的段是候选段。这是由于如果选取了更长的段,我们显然可以用这个段将它分开,并且分开的两段中也恰有一段满足逆序对个数比长度少一。
接下来我们要保证al…r==[l,r]
用线段树维护即可
接下来剩下的就是对[l,r]检查逆序对个数是否为r-l。
注意到[𝑙, 𝑟]元素恰好为[𝑙, 𝑟],所以我们只要求出[1, 𝑟]的逆序对,减去
[1,𝑙 − 1]的逆序对,而 1, 𝑙 − 1 × [𝑙, 𝑟]贡献的逆序对个数就是[1, 𝑙 − 1]中大于r的元素个数乘以r-l+1,所以我们只要离线或可持久化线段树维护即可。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+77;
struct Tr
{
	int l,r,w;
}tr[N*20];
int tot,a[N],n,f[N],rt[N],st[N][18],c[N],logn[N],cnt,pos[N],g[N],L[N],pre[N],ans[N];
ll b[N];
map<ll,int> lst;
void change(int x,int v)
{
	for(int i=x; i; i-=i&-i) c[i]+=v;
}
int query(int x)
{
	int yjy=0;
	for(int i=x; i<=n; i+=i&-i) yjy+=c[i];
	return yjy;
}
void ins(int y,int &x,int l,int r,int s)
{
	tr[x=++cnt]=tr[y];
	tr[x].w++;
	if(l==r) return;
	int mid=l+r>>1;
	if(s<=mid) ins(tr[y].l,tr[x].l,l,mid,s);
	else ins(tr[y].r,tr[x].r,mid+1,r,s);
}

int query(int x,int l,int r,int s,int t)
{
	if(l==s&&r==t) return tr[x].w;
	int mid=l+r>>1;
	if(t<=mid) return query(tr[x].l,l,mid,s,t);
	else if(s>mid) return query(tr[x].r,mid + 1,r,s,t);
	else return query(tr[x].l,l,mid,s,mid) + query(tr[x].r,mid + 1,r,mid + 1,t);
}

void init()
{
	logn[0]=-1;
	for(int i=1; i<=n; i++) logn[i]=logn[i>>1]+1,st[i][0]=a[i];
	for(int j=1; (1<<j)<=n; j++) for(int i=1; i+(1<<j)-1<=n; i++)
		st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}

int ask(int l,int r)
{
	int k=logn[r-l+1];
	return max(st[l][k],st[r-(1<<k)+1][k]);
}

int main()
{
	freopen("pm.in","r",stdin); freopen("pm.out","w",stdout);
	ll sum=0;
	scanf("%d",&n); 
	lst[0]=1;
	for(int i=1; i<=n; i++)
	{
		scanf("%d",&a[i]);
		ins(rt[i-1],rt[i],1,n,a[i]);
		b[i]=b[i-1]+query(a[i]+1);
		change(a[i],1);
		sum+=a[i]-i;
		L[i]=lst[sum];
		lst[sum]=i+1;
		pos[a[i]]=i;
	}
	init(); 
	for(int i=1; i<=n; i++)
	{
		if(L[i])
		{
			int l=L[i],r=i;
			ll yjy=b[r]-b[l - 1];
			yjy-=(ll)(r-l+1)*query(rt[l-1],1,n,r,n);
			if(yjy==r-l&&ask(l,r)==r)
			{
				f[i]=f[pre[l-1]]+1;
				g[i]=pre[l-1];
			}
		}
		pre[i]=pre[i-1];
		if(f[pre[i-1]]<f[i]) pre[i]=i;
	}
	int r=pre[n];
	while(r)
	{
		int l=L[r];
		for(int i=r; i>=l; i--)
		{
			int x=pos[i];
			for(int j=x; j<i; j++)
			{
				swap(a[j],a[j+1]);
				ans[++tot]=j;
				pos[a[j]]=j;
				pos[a[j+1]]=j+1;
			}
		}
		r=g[r];
	}
	printf("%d\n",tot);
	for(int i=1; i<=tot; i++) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值