Codeforces Round 421 Div.2 D. Mister B and PR Shifts 思维 乱搞

题目地址:

http://codeforces.com/contest/820/problem/D

题意:

给一个由 [1,n] n(2<=n<=106) 个数组成的排列p,定义这个排列的差值是 i=ni=1|p[i]i| ,现在我们可以对这个排列进行平移(把排列的最后一个数放到排列的最前面),问这个排列的最小差值是多少。

思路:

这题看题解还有点迷,其实也不是很难,就是要自己动手码,代码多改改,多思考下应该都能做出来。
自己随便写几个排列模拟下,不难发现,一次平移其实就是 p[i]>i i 的值自增了1(除了最后面那个数要特殊考虑),那么所有 p[i]i>0 的数都会使 cost[i]=|p[i]i| 小1,反之,所有 p[i]i<=0 的数都会使 cost[i]=|p[i]i| 加1,那么我们用一个pos记录 p[i]i>0 的数的个数,neg记录 p[i]i<=0 的个数。

特殊考虑一下最后一个数,这个数的 cost[n]=|p[i]n|=np[i] ,一定是neg,又因为我们要对它特殊考虑,所以不在 pos neg 的计数中考虑,所以每次平移 sum=sumpos+(neg1) 。并且 p[n] 移到了 1 的位置,所以特判一下 p[n] 的大小,对 pos neg 进行修改。

接着就是特殊考虑 p[n] cost 的影响,很简单, sum=sum|p[n]n|+p[n]1 就好了。

最后还是 pos neg 值的变化,我们用一个数组 ones[] 记录 p[i]i 的值,每次把当前差值为1的数从 pos 中拿出,放入 neg 中就好了。特别注意 ones[] 数组要开两倍大小。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cstdlib>

using namespace std;

//#define abs(x) ((x) < 0 ? -(x) : (x))
#define MS(x, y) memset(x, y, sizeof(x))

typedef long long LL;
const int MAXN = 1e6 + 5;

int n;
int p[MAXN], ones[MAXN << 1];

int main() {
  while (~scanf("%d", &n)) {
    LL sum = 0, mn;
    int mn_idx = 0, pos = 0, neg = 0;
    MS(ones, 0);
    for (int i = 1; i <= n; ++i) {
      scanf("%d", p + i);
      sum += abs(p[i] - i);
      if (p[i] > i) ++pos;
      else ++neg;
      if (p[i] >= i) ++ones[p[i] - i];
    }
    mn = sum;
    for (int i = 1; i <= n; ++i) {
      sum = sum - pos + neg - 1;
      sum = sum - abs(p[n - i + 1] - n) + p[n - i + 1] - 1;
      if (p[n - i + 1] != 1) {
        ++pos;
        --neg;
      }
      pos -= ones[i];
      neg += ones[i];
      ++ones[p[n - i + 1] - 1 + i];
      if (sum < mn) {
        mn = sum;
        mn_idx = i;
      }
    }
    printf("%I64d %d\n", mn, mn_idx);
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值