火柴排队:离散化&逆序对

P1966 火柴排队

https://www.luogu.com.cn/problem/P1966

其实题目意思就是给你两列数,通过交换相邻两个数的位置,上下两个对应的数会有一个差的平方,假设就叫k吧,每两个对应的数就会有个k,最后要求所有k的总和要最小,问最少需要交换多少次?

分析:

想要最后的和小,那么每一个k都要尽量小(废话)。遵循大对大,小对小的原则(好像叫方差…)。其实我们在乎的不是这个数本身的数值,而是它在数列中的排名。就比如A数列的最大的,对应B数列中最大的,A数列中最小的,对应B数列中最小的…以此类推。所以这就要用到离散化的思想。

思路:

让我们来简单分析一下样例2:
A: 1 3 4 2
然后我们将它按大小编个码:
编码:1# 3# 4# 2#

A :   1   3   4   2
code: 1#  3#  4#  2#
//这里code表示的是排名,
//例如1是最小的排第一位,所以是1#;
//2是第二小的排第二位,所以是2# 

然后我们对第二组数动手术

A :   1   3   4   2
code: 1#  3#  4#  2#
//这里code表示的是排名,例如1是最小的排第一位,所以是1#;2是第二小的排第二位,所以是2#
B:    1  7  2  4
下标: 1# 2# 3# 4#
hope: 1# 4# 2# 3# 
//下标就是下标(后面求逆序对有用) 
//hope是我们希望排成的样子,就是跟上面的1 3 4 2对应起来:(大对大,小对小) 
//首先对A数组的1#,我们要拿最小的数去对,B数组中最小的数是B[1]=1,它的下标是1,所以填1#
//再举个例子,比如我们要对A的4#(排行老4,最大的),我们要拿B数组最大的去对,找到B[2]=7
//是最大的,那么记录下它的下标就是2#
//最后我们所hope的下标顺序就是1 4 2 3。 

然后我们就要考虑调换的问题。本来B数组的排列方式是1 2 3 4(下标),现在我们所hope的是1 4 2 3,这该怎么算呢?
其实我们不妨反过来想,与其想怎么从1 2 3 4变成 1 4 2 3,不如想怎么从1 4 2 3变成1 2 3 4这就变成了一个逆序对的事,只要求1 4 2 3中的逆序对个数就是调换次数。

逆序对:

所谓逆序对就是“反过来”。例如1 3不是,但3 1就是逆序对,(字面意思)
逆序对的一般解法就是打勾数组+前缀和,反过来统计就行了(但这题可以优化)我先来解释一下常规做法:

1 4 2 3的逆序对

0  1   2   3   4 

   1       
//一开始1读了进来,在1那儿打了勾。
//然后从右往左统计1之后的的后缀和,为0,不做动作,继续
0  1   2   3   4 

   1           1
//读入4 打勾,后缀和0,继续
0  1   2   3   4 

   1   1        1
// 读入2,打勾,发现2后面的4已经在了,后缀和为1,说明发现了一个逆序对,ans++
0  1   2   3   4 

   1   1   1   1
//读入3,打勾,发现3后面的 4已经在了,后缀和1,又是一个逆序对,ans++
//最后ans=2,符合题意  

优化

以上这道题的整体思路就介绍完了。如果你绕出来了,恭喜你!但是很重要的一点是如果模拟以上思路,你的代码会非常难写!所以这就需要优化。
在离散化的时候进行排序。

// A:      1    3   4   2 
//下标     1#, 2#, 3#, 4#

//sort     1    2   3  4
// 下标    1#, 4#, 2#, 3#

// B:     1   7   2   4 
//下标    1#, 2#, 3#, 4#

//sort    1   2   4    7 
//下标    1#  3#  4#  2#
//存入id 1,4,2,3 一一对应
for(int i=1;i<=n;i++) id[b[i].second]=a[i].second;//second是下标
//大家可以自己走一遍,其实最后存入id的就是1 4 2 3,就是我们要求逆序对的那个东西

然后逆序对也是可以优化的(不叫优化吧)其实就是改变一个顺序,从前往后求逆序对,统计前缀和而已。树状数组可以简单地求逆序对和前缀和。

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pair_;
const int MAXN=1e5+5;
const int mod=99999997;

int n;
pair_ a[MAXN],b[MAXN];
int c[MAXN],id[MAXN];
ll ans;

inline int lowbit(int x)
{
	return x&(-x);
}

inline void add(int x,int v)
{
	while(x<=n)
	{
		c[x]+=v;
		c[x]%=mod;
		x+=lowbit(x);
	}
}

inline int pre_sum(int x)
{
	int res=0;
	while(x>=1)
	{
		res+=c[x];
		res%=mod;
		x-=lowbit(x);
	}
	return res;
}

inline bool cmp(pair_ x,pair_ y)
{
	return x.first<y.first;
}


int main()
{
	cin>>n;
	
	for(int i=1;i<=n;i++)
	{
		cin>>a[i].first; 
		a[i].second=i;
	}
	
	for(int i=1;i<=n;i++)
	{
		cin>>b[i].first;
		b[i].second=i;
	}
	
	sort(a+1,a+n+1,cmp);
	sort(b+1,b+n+1,cmp);
	
	for(int i=1;i<=n;i++) id[b[i].second]=a[i].second;
	
	for(int i=1;i<=n;i++)
	{
		add(id[i],1);
		ans=ans+i-pre_sum(id[i]);
		ans%=mod;
	}
	
	cout<<ans;
	return 0;
 } 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值