CodeForces 820D Mister B and PR Shifts(思维题)

博客讲述了如何高效解决CodeForces上的一个问题,即寻找一个排列在循环右移过程中的最小值及其出现次数。作者最初尝试了O(N^2)的解决方案,但发现超时。通过研究其他代码,找到了一个O(N)的思路,利用cnt数组记录每个数移到其实际下标所需移动次数,并用add和sub变量动态维护移位后sum值的变化。关键在于理解每个数的位置对于移位后绝对差值的影响,特别是位置num[i]是变化的转折点。
摘要由CSDN通过智能技术生成

题意:给定一个长度为n的排列,每次向右循环移位,求的最小值,并求出是在第几次移位得到的。

思路:开始O(N^2)模拟了一下,果然超时。参考了一下网上代码,知道了一个O(N)的思路。用cnt数组记录当前num[i]所在位置 到 下标为num[i]的位置 需要向右循环移位几次,两个变量add、sub表示下一次移位会使sum值+1/-1的个数,每次更新均在上一排列的基础上进行。

先注意题意, 1 <= num[i] <= n。

再注意一点,对于每个位置的数,设为num[i],先不看num[i] = 0或者num[i] = n的情况,对于其他情况,从1到num[i]这段区间,num[i]右移使|num[i] - loc|单减,从num[i]到n这段区间,num[i]右移使|num[i] - loc|单增;对于num[i] = 0,从1到n,|num[i] - loc|一直单增;对于num[i] = n,num[i]右移使|num[i] - loc|单减;当num[i]从n移动到1,增减不一定。所以对于每个数num[i],位置num[i]是个转折点,决定下一步|num[i] - loc|如何变化(每次+1还是-1)。具体思路见注释。

// CodeForces 820D Mister B and PR Shifts 运行/限制:264ms/2000ms
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
int num[1000005],cnt[1000005];
int main(){
	int n;
	int add, sub, index;
	LL sum, re;
	while (scanf("%d", &n) != EOF) {
		sum = 0; add = 0; sub = 0;
		memset(cnt, 0, sizeof(cnt));
		for (int i = 1; i <= n; i++) {
			scanf("%d", &num[i]);
			sum += fabs(num[i] - i);
			cnt[(num[i] - i + n) % n]++;//cnt下标为:当前num[i]所在位置 到 num[i]位置 需要移位几次
			if (num[i] > i) sub++;//下一次移位会使值-1的个数
			else add++;//下一次移位会使值+1的个数
		}
		re = sum; index = 0;
		for (int i = 1; i < n; i++) {//每次在前一次移位的基础上处理,i为第几次移动
			sum += add - sub - 1;//当前最后一位肯定满足数值小于等于n(数据范围最大是n),所以之前计算在add里了;但因为要移动到首位,所以不一定是加、减
			sum += (num[n - i + 1] - 1) - (n - num[n - i + 1]);//计算当前最后一位移动到首位sum值的变化;
			                                                   //每次并没有真正进行移位,而是每次计算最后一位应该是未移位序列的哪个位置了

			//求完sum,相当于该次移动了

			//根据此次移动后的结果,更新下一步add、sub会出现的值
			//-1,+1是因为这一次移动后到第一个位置的数,由于单调性反转,使add比正常情况-1、sub+1。
			//num[i] = 1时,|num[i] - loc|全程单调递增,从n位置移动到1位置时单调性的变化是个特例,可以分析一下num[n] = 1也满足下面式子
			add = add + cnt[i] - 1;
			sub = sub - cnt[i] + 1;
			if (sum < re) {
				re = sum;
				index = i;
			}
		}
		printf("%lld %d\n", re, index);
	}
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值