“新智认知”杯上海高校程序设计竞赛暨第十七届上海大学程序设计春季联赛----E-CSL的魔法

首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/551/E
来源:牛客网

题目如下:
题目内容
描述
很明显,如果要满足a0b0+a1b1+·····+anbnn最小

我们就要按如下方式进行对应:

a0为a序列的最大(小)值 且 b0为b序列的最小(大)值
a1为a序列的第二大(小)值 且 b1为b序列的第二小(大)值
a2为a序列的第三大(小)值 且 b2为b序列的第三小(大)值
·
·

an为a序列的最小(大)值 且 bn为b序列的最大(小)值

我们可以以一个序列为基准,只交换另一个序列里的元素,使被交换元素的这个序列里的元素按照如上对应方式与基准序列进行严格的对应(举个例子,比如a序列为{5,2,4,3,1},b序列为{4,1,3,5,2},我们可以让a序列不动,将b序列进行元素交换,使b序列变成{1,4,2,3,5}即可,交换b序列元素的次数即为答案

而问题在于,如何得到这个次数cnt的值


思路:

可以将初始序列每个元素加一个独一无二的标记(标记最好是升序数值,或者降序数值),为了方便,我们给每个元素的标记数设为此元素在当前序列中的位置(比如刚刚那个a序列我们加上标记之后就变成了{{5,1},{2,2},{4,3},{3,4},{1,5}},使序列成为一个个数对,数对的第一个值是数值大小,第二个值是这个数在a序列的位置(数值标记),同理,b序列变成了{{4,1},{1,2},{3,3},{5,4},{2,5}})

那么标记之后有什么用捏?

我们将两个序列关于数值大小排序,使得两个序列中一个序列数值大小为升序排列,一个数值大小为降序排列(选哪个序列是任意的),当排完之后,数对的数值大小就是有序的了,而数值标记则成为了乱序

排序后,a序列(升序)为{{1,5},{2,2},{3,4},{4,3},{5,1}},b序列(降序)为{{5,4},{4,1},{3,3},{2,5},{1,2}};

由于我们只在意两个序列对应位置元素乘积的和的最值,故不用考虑序列本身元素是否有序(集合的无序性),因此基准序列(我们假设a序列是基准序列)在排序前和排序后在此题是等效的,而另一个序列(排序后的b序列)则成为了原来序列(排序前的b序列)经过cnt次交换数值得到的与基准序列(a序列)进行严格对应的序列。

要求cnt的最小值,我们可以进行模拟,将排序后的b序列通过贪心交换法,变成排序前的b序列,每次交换,cnt就加一,那这个cnt就是最后的答案。

如何贪心交换?这时序列的标记就起了作用,我们只要让b序列和基准序列(a序列)的数值标记对应相等就行了
a序列不变:
{{1,5},{2,2},{3,4},{4,3},{5,1}}
让b序列:
{{5,4},{4,1},{3,3},{2,5},{1,2}}
进过交换数对,成为新的b序列:
{{2,5},{1,2},{5,4},{3,3},{4,1}}
两个序列数据标记对应相等,就像未排序前的两个序列数据标记对应相等一样

因此,我们现在只用考虑数值标记而可以忽略数值大小。

为了使对应数组标记相等,我们重新定义一个数组diff,数组diff存b序列元素的数值标记,数组diff下标为b序列排序后的数值标记(即diff[a序列数值下标]=b序列对应数值下标,对于这个例子diff[5]=4,diff[2]=1,diff[4]=3,diff[3]=5,diff[1]=2

将这个这个数组变成diff[i]=i的最小步骤数cnt即为答案,很简单的模拟排序问题。


cnt初始化等于0,遍历diff数组,如果发现diff[i]!=i,就重新定义一个t,将diff[i]的值赋给t,然后令diff[i]=0(相当于我们把diff[i]的值拿出来,用0来表示diff[i]为空),然后开始循环断diff[t]是否等于t,如果diff[t]不等于t,就交换diff[t]和t的值,并将cnt++(相当于把原来diff[t]的值取出来,然后令diff[t]=t,千万不要写成下面样子)

t=diff[t];//这样写就把t的值首先给改变了
diff[t]=t;

可以按下面这样写

int x=diff[t];
diff[t]=t;
t=x;//这样写就先把diff[t]的值取出来放到一边,令diff[t]=t,在把放到一边的值存到t里面。

也可以用swap语句

swap(diff[t],t);

这样循环判断,直到当diff[t]=0的时候(这样搬动数据是一个循环,一定会出现diff[t]=0),就直接令diff[t]=t;

然后往下继续遍历数组diff


过程:

idiff[i]
12
21
35
43
54
t

diff[1]!=1 (diff[1]=2),于是将diff[1]的值剪切给t

idiff[i]
10
21
35
43
54
t2

diff[t]!=t(diff[t]=1),swap[diff[t],t](交换值),cnt++

idiff[i]
10
22
35
43
54
t1

diff[t]=0,将t的值剪切给diff[t]

idiff[i]
11
22
35
43
54
t

diff[3]!=3(diff[3]=5),于是将diff[3]的值剪切给t

idiff[i]
11
22
30
43
54
t5

diff[t]!=t(diff[t]=4),swap[diff[t],t](交换值),cnt++

idiff[i]
11
22
30
43
55
t4

diff[t]!=t(diff[t]=3),swap[diff[t],t](交换值),cnt++

idiff[i]
11
22
30
44
55
t3

diff[t]=0,将t的值剪切给diff[t]

idiff[i]
11
22
33
44
55
t

结束,cnt=3


代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int,int> P;//使用STL库里的数对存数值大小和数值标记,其中first为数值大小,second为数值标记
P magic1[100005],magic2[100005];//两个数对类型的序列
int sign[100005]={0}; //之前说的只用了存两个序列数值标记的数组,其中数组下表是a序列的数值标记,数组存的值为b序列数组标记
int cnt=0;//输出的结果
int t,n;
bool comp1(P a,P b){
	return a.first>b.first;
}
bool comp2(P a,P b){
	return a.first<b.first;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>t;
		magic1[i].first=t;
		magic1[i].second=i;
	}
	for(int i=1;i<=n;i++){
		cin>>t;
		magic2[i].first=t;
		magic2[i].second=i;
	}//对两个数组进行赋值
	sort(magic1+1,magic1+n+1,comp1);
	sort(magic2+1,magic2+n+1,comp2);//对两个数组关于数值大小排序
	for(int i=1;i<=n;i++)
		sign[magic1[i].second]=magic2[i].second;//将两个序列的数值标记拿出来
	for(int i=1;i<=n;i++){//开始贪心排序,遍历sign数组
		if(sign[i]!=i){
			t=sign[i];
			sign[i]=0;//将sign[i]的值拿出来,那么sign[i]没有值,为空(用0来表示)
			while(sign[t]!=t && sign[t]!=0){
			//while(sign[t]存的值不是t && sign[t]不是空的)
				swap(sign[t],t);//将sign[t]存的值拿出来,把sign[t]应该存的值丢进去
				cnt++;//次数加一
			}
			sign[t]=t;//循环直到sign[i]为空时停止(表面sign[t]为开始拿出来的值的位置,此时t应该等于i)
		}	
	}
	cout<<cnt;
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值