P6170 [USACO16FEB] Circular Barn G

题目 [ 传送门 ]

        题目描述

        作为当代建筑的爱好者,Farmer John 建造了一个圆形新谷仓,谷仓内部 n个房间排成环形\left ( 3\leq n\leq 10^{5}\right ),按顺时针顺序编号为1...n,每个房间都有通往与其相邻的左右房间的门,还有一扇门通往外面。

        现在 FJ 有 n 头奶牛,他的目标是让每个房间恰好有一头奶牛。然而不幸的是,现在奶牛们随意呆在某个房间里,第 i 个房间里有 ci​ 头奶牛。保证 \sum ci=n

        FJ 决定采用这样的方法来解决这个问题:让某些奶牛顺时针穿过某些房间到达指定的位置。如果一头奶牛穿过了 d 扇门,他消耗的能量为 d^{2}。你需要帮 FJ 算出所有奶牛消耗的能量和最小值是多少。

        输入格式

        第一行一个整数 n,接下来 n行,第 i行一个整数 ci​。

        输出格式 

        输出所有奶牛最小消耗能量和。

分析

        有一个比较明显的贪心,设a,b,c为3个位置,a<b<c,那么a->b,b->c一定比a->c优,

简单的证明一下:

                设a,b间距离为x,  b,c间距离为y

                则a->b,b->c 的ans1=x^2+y^2 ,a->c的ans2=(x+y)^2

                显然,ans1<ans2

        那么,我们只需要确定了起点,就可以直接计算答案。

        于是,现在的问题就在于怎么求起点(起点的定义为以该点出发,最后在不回到该点的情况下,可以完成交换)。 不难发现,起点的ai一定为0(感性理解一下),并且任意一个点到起点的总个数应该小于等于当前已经经过的位置数(否则多余的牛一定会对起点前的位置产生影响)。通过这2点就可以找到起点。 于是:

Code

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,s,id,ans,a[100005],b[100005],c[100005],t,l;
int main()
{
	scanf("%lld",&n); id=n;
	for(int i=1;i<=n;i++) scanf("%lld",a+i);
	for(int i=n;i>=1;i--)
	{
		s+=a[i]; if(s>(id-i)) id=i-1,s=0;
		if(a[id]!=0 && a[i]==0) id=i;
	}
	while(a[id]!=0) id--;//找起点
	for(int i=id;i>=1;i--) b[++t]=a[i];
	for(int i=n;i>id;i--) b[++t]=a[i]; t=0;
    //把起点作为第一位,重构数组
	for(int i=1;i<=n;i++)
	{
		if(b[i]==0) c[++t]=i;//c[]数组表示没有牛的位置
		else
		{
			int cnt=0;
			for(int j=1;j<=b[i] && l<t;j++) l++,ans+=(i-c[l])*(i-c[l]),cnt++;
			if(cnt==b[i]) c[++t]=i;
            //每次都走到第一个位置一定最优
		}
	}
	cout<<ans;
	return 0;
}

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值