UOJ Logo zhouzixuan的博客

博客

bzoj 4412: [Usaco2016 Feb]Circular Barn

2016-02-27 16:59:38 By zhouzixuan
【题目描述】:
    有一个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;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。