test 排序(二分答案)


题解:二分答案。
这道题貌似是某年IOI的弱化版。
有一个非常厉害也非常好用的结论:对于游戏的一个形如“ABABABABA.."的操作序列,在所有A操作不变得前提下,可以找到一个形如”AAAAABBBBB"的操作序列,使两个序列作用于同一个排列的效果相同。
有了这个结论的基础这个题就好做多了。问题就转化成了最少需要几次操作将序列变成升序(可以交换不相邻的,如果只能交换相邻的就变成了树状数组求逆序对)
这个需要怎么搞呢?我们可以从前向后扫,如果当前位置的数不是应该在这个位置的数,就找到应该在这个位置的数,然后将其交换,最终交换了几次最少的操作次数就是几。
但是还有别的方法:
以5 4 3 2 1 为例
我们可以发现5,1虽然不在自己应该在的位置,但是如果把它们两个看成整体,对于整个序列来说它们占据了排好序后5,1应该在的位置,所以对于整个序列来说是有序的,它们只是自身内部无序而已。5应该到1处,1应该到5处,形成了一个循环,所以可以将它们抽象成一个环,环内换序就可以了。(下面把这种环称为循环节) 
对于一个含有n个元素的循环节来说,要使其有序,要交换n-1次(前面都排好了,最后一个数自然有序就不用排了)。 
上例中3在原本就在的位置,可以看成一个元素的循环节。 
我们可以推断出有一个循环节,就可以少交换一次,因为n个元素的循环节,只需交换n-1次即可有序。 
那么对于整个序列来说,最少交换次数为 元素总数-循环节个数。那么例子中的答案就是2.
然后这道题其实就只需要二分答案然后判定了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 600003
using namespace std;
int x[N],y[N],a[N],b[N],pos[N],n,m;
bool pd(int num)
{
	for (int i=1;i<=n;i++) b[i]=a[i];
	for (int i=1;i<=num;i++) swap(b[x[i]],b[y[i]]);
	for (int i=1;i<=n;i++) pos[b[i]]=i;
	int t=0;
	for (int i=1;i<=n;i++) 
	 if (b[i]!=i) {
	  int now=b[i]; int p=pos[i];
	  swap(b[i],b[pos[i]]),t++;
	  pos[now]=p;
    }
	if (t<=num) return true;
	return false;
}
int main()
{
	freopen("sort.in","r",stdin);
	freopen("sort.out","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]++;
	scanf("%d",&m);
	for (int i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]),x[i]++,y[i]++;
	int l=0; int r=m; int ans=r;
	while(l<=r) {
		int mid=(l+r)/2;
		if (pd(mid)) ans=min(ans,mid),r=mid-1;
		else l=mid+1;
	}
	printf("%d\n",ans);
}


#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=610000;
int n,m,i,a[N],s[N],x[N],y[N],vis[N];
int check()
{
	int ret=0;
	memset(vis,0,sizeof(vis));
	for (int i=0;i<n;i++)
	{
		if (vis[i]) continue;
		int x=a[i],j=a[i];
		while (a[j]!=x) {ret++;vis[j]=1;j=a[j];}
	}
	return ret;
}
int main()
{
	freopen("sort.in","r",stdin);
	freopen("sort.out","w",stdout);
	scanf("%d",&n);
	for (i=0;i<n;i++) scanf("%d",&s[i]);
	scanf("%d",&m);
	for (i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]);
	int l=0,r=m;
	while (l<r)
	{
		int mid=(l+r)>>1;
		for (i=0;i<n;i++) a[i]=s[i];
		for (i=1;i<=mid;i++) swap(a[x[i]],a[y[i]]);
		if (check()<=mid) r=mid;
			else l=mid+1;
	}
	cout<<l<<endl;
	return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值