蓝桥杯每日一题——Acwing 6131. 农夫约翰最喜欢的操作

题目:

 思路:

发现同余与商无关,再将其模到0~M-1之间,再将其放到一个环上,放到环上后发现,对于任意x,穿过x做一条对称轴,对称轴对面的两个点之间一定是不会有任何路径穿过,可以从对面的这条边断开,将二维变成经典一维问题。

所以可以反过来思考,只要枚举所有能断开的位置,再把所有能断开的位置的x枚举一遍,则一定可以把最优解枚举到.

怎么枚举所有能断开的位置?
先将所有a[i]排序后(因为能断开的位置一定是相邻两个a[i]之间的位置)放在圆周上,枚举所有相邻a[i]的边,假设其断开,断开后根据一维情况最优解求答案,即求中位数,然后求所有点到中位数的距离和,并将所有情况的中位数对比求最小值,就是最少操作数。

但是,在圆上断开时最坏情况下有n个位置,再排序后求每点到中位数的总距离和的时间复杂度为n^2logn,超时,需要优化:

枚举换环上从每个位置断开,有一个通用的优化方式

破环成链:将环直接展开,展开后再复制一遍放在右边成一个2n的数组,并且递增排序,如下图

每一个从环上断开后所形成的链都对应着这个数组上一个长度为n的区间,因此如果要枚举所有断开位置所形成的链的话,相当于枚举这个长度为2n的数组中所有长度为n的区间。然后对于每一个长度为n的区间都求其中位数p,再都分别求每个数到中位数的距离和,如下图

但是这里如果直接求的话时间复杂度还是n^2,需要优化

通过公式变化+前缀和来进行优化,时间复杂度变为n,如下图

代码:

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

typedef long long LL;

const int N=400010;//因为要把数组乘2,N多开一倍
 
int n,m;
int a[N];
LL s[N];//前缀和
 
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
			a[i]%=m;
		}
		sort(a+1,a+n+1);//排序,保证后面枚举的是相邻边 
		//破环成链,将数组复制一遍放在后面 
		for(int i=1;i<=n;i++) a[i+n]=a[i]+m;
		
		//求前缀和
		for(int i=1;i<=n*2;i++) s[i]=s[i-1]+a[i];
		
		//定义答案,先定义为正无穷
		LL res=1e18;
		//枚举所有长度为n的区间,枚举左端点,右端点等于左端点+n-1,共同构成长度为n的区间
		for(int l=1;l<=n;l++)
		{
			int r=l+n-1,p=(l+r)/2;//右端点,中点(中位数)
			//左、右半边的和,a[p]要强制转换为LL,否则会爆int
			LL left=(p-l+1)*(LL)a[p] - (s[p]-s[l-1]);
			LL right=(s[r]-s[p]) - (r-p)*(LL)a[p];
			//更新最小值
			res=min(res,left+right); 
		 } 
	cout<<res<<endl;
	}
	return 0;
}

有类似思想的题目:AcWing 104. 货仓选址

题目:区间内每点到中点的距离和最小

思路:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值