[CF819B]Mister B and PR Shifts

题意:定义一个排列$p_{1\cdots n}$的“偏移量”$D=\sum _{i=1}^n\left|p_i-i\right|$

求它所有的轮换排列中偏移量最小的是多少,要求输出轮换序数

 

暴力就是求出每个轮换排列然后计算$D$,我们不妨换个视角,看看如何计算每个$p_k$对不同排列的$D$的贡献

设$d_i$是轮换序数为$i$的轮换排列的偏移量(轮换从右往左),当前处理到$p_k$

#1若$p_k\geq k$

对$0\leq i\leq k-1$,贡献为$p_k-k+i$

对$k\leq i\leq k+n-p_k$,贡献为$n+k-p_k-i$

对$k+n-p_k+1\leq i\leq n-1$,贡献为$p_k-n-k+i$

#2若$p_k\lt k$

对$0\leq i\leq k-p_k-1$,贡献为$k-p_k-i$

对$k-p_k\leq i\leq k-1$,贡献为$p_k-k+i$

对$k\leq i\leq n-1$,贡献为$n+k-p_k-i$

区间加和,还带关于$i$的线性的项,直接打两个标记(常数,$i$的系数),最后扫一遍即可

#include<stdio.h>
#define ll long long
int p[2000010];
ll dc[4000010],dx[4000010];
ll min(ll a,ll b){return a<b?a:b;}
int main(){
	int n,i,pos;
	ll del,cnt,ans;
	scanf("%d",&n);
	for(i=1;i<=n;i++)scanf("%d",p+i);
	for(i=1;i<=n;i++){
		if(p[i]>=i){
			dc[0]+=(p[i]-i);
			dc[i]-=(p[i]-i);
			dx[0]++;
			dx[i]--;
			dc[i]+=(n+i-p[i]);
			dc[i+n-p[i]+1]-=(n+i-p[i]);
			dx[i]--;
			dx[i+n-p[i]+1]++;
			if(i+n-p[i]+1<=n-1){
				dc[i+n-p[i]+1]+=(p[i]-n-i);
				dx[i+n-p[i]+1]++;
			}
		}else{
			dc[0]+=(i-p[i]);
			dc[i-p[i]]-=(i-p[i]);
			dx[0]--;
			dx[i-p[i]]++;
			dc[i-p[i]]+=(p[i]-i);
			dc[i]-=(p[i]-i);
			dx[i-p[i]]++;
			dx[i]--;
			if(i<=n-1){
				dc[i]+=(n+i-p[i]);
				dx[i]--;
			}
		}
	}
	del=cnt=0;
	ans=9223372036854775807ll;
	for(i=0;i<n;i++){
		del+=dx[i];
		cnt+=dc[i];
		if(cnt+i*del<ans){
			ans=cnt+i*del;
			pos=i;
		}
	}
	printf("%I64d ",ans);
	if(pos==0)
		putchar('0');
	else
		printf("%d",n-pos);
}

转载于:https://www.cnblogs.com/jefflyy/p/7793630.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值