【题目描述】:
有一个N个点的环,相邻两个点距离是1。点顺时针标号为1..N。
每一个点有ci头牛,保证∑ci=N。
每头牛都可以顺时针走。设一头牛走了d个单位停下了,将耗费d^2的能量。
请设计一种牛的走法,使得每一个点上都正好有一头牛,且最小化耗费的能量。
【题目解法】:
我是乱搞的TAT
我们考虑枚举环的起点,这样所有的奶牛都只能往右边走
然后考虑一个奶牛,他在i,要走到j,在[i,j]之间还有一个位置k的奶牛没有走过
显然x^2+y^2<=(x+y)^2,因此我们先i->k,然后换一个奶牛k->j,这样最优
因此我们从后往前枚举,如果一个点的奶牛数量为1,显然合法跳过,如果>1显然该起点不合法
如果为0,我们从前面最近的地方拉一条奶牛过来即可,复杂度O(N)
然后总的复杂度O(N^2)
题目没有范围。一开始一位这一定是水题,然后就TLE了
我们考虑事实上对于某个起点开始的序列可能不合法
考虑前缀和sum[i]>=i对任意i成立合法
我们将原数组两倍之后枚举起点i,对于[i,i+n-1]中任意一个点j都要满足sum[j]-sum[i-1]>=j-(i-1)
也就是sum[j]-j>=sum[i-1]-(i-1),右边和j无关,因此我们求的sum[j]-j的区间最小值即可
维护一个线段树,判断是否合法,实际上合法的情况很少
然后就飞快的跑过去了TAT
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=200005;
const long long INF=1e15;
struct node {int mint;} tree[N*4];
int n,m; long long ans,a[N],b[N],sum[N],nowans;
inline long long getint()
{
long long x=0; char c=getchar(); bool flag=false;
while ((c!='-')&&((c<'0')||(c>'9'))) c=getchar();
if (c=='-') flag=true,c=getchar();
while ((c>='0')&&(c<='9')) x=x*10+(long long)(c-'0'),c=getchar();
if (flag) return -x; else return x;
}
void init()
{
n=getint(); m=n+n; ans=INF;
for (int i=1; i<=n; i++) b[i]=b[n+i]=getint();
for (int i=1; i<=m; i++) sum[i]=sum[i-1]+b[i];
}
void build(int p,int l,int r)
{
if (l==r) {tree[p].mint=sum[l]-l; return;}
int mid=(l+r)/2; build(p*2,l,mid); build(p*2+1,mid+1,r);
tree[p].mint=min(tree[p*2].mint,tree[p*2+1].mint);
}
int ask(int p,int l,int r,int ll,int rr)
{
if ((l==ll)&&(r==rr)) return tree[p].mint;
int mid=(l+r)/2;
if (rr<=mid) return ask(p*2,l,mid,ll,rr);
else if (ll>mid) return ask(p*2+1,mid+1,r,ll,rr);
else return min(ask(p*2,l,mid,ll,mid),ask(p*2+1,mid+1,r,mid+1,rr));
}
long long calc()
{
if (a[1]==0) return INF; long long far=0;
for (int i=1; i<=n; i++) if (a[i]>0) far=i;
if (far==0) return 0; long long nowans=0;
for (int i=n; i>=1; i--)
{
if (a[i]>1) return INF; if (a[i]==1) continue;
if (far>=i)
{
far=i-1;
for (;(far>0)&&(a[far]==0);far--);
}
if (a[far]==0) return INF;
a[i]++; nowans=nowans+(long long)(far-i)*(far-i);
if (nowans>=ans) return nowans;
a[far]--; for (;(far>0)&&(a[far]==0);far--);
}
return nowans;
}
void solve()
{
int cnt=0;
for (int i=1; i<=n; i++)
{
int nowans=ask(1,1,m,i,n+i-1);
if (nowans<sum[i-1]-(i-1)) continue;
for (int j=1; j<=n; j++) a[j]=b[i+j-1];
ans=min(ans,calc());
}
printf("%lld\n",ans); //I64d->lld
}
int main()
{
init();
build(1,1,m);
solve();
return 0;
}