zoukankan      html  css  js  c++  java
  • [四连测(三)]圆形谷仓

    我复制了图片,样例什么的就不要在意了。

    解题思路

    也许有人很迷惑,这个样例怎么算出来的,至少都是上百啊,我身边是有人跟我这样说过的,确实,题目描述有一点问题。它说的是“每头奶牛都待在一个房间”,这句话是有歧义的。它没说是都在“一个房间”,还是均匀地在“每一个房间”。这道题呢,是均匀地在每一个房间,运用一下贪心,模拟什么的,绝对是可以弄出样例的。

    其实,这道题的实际就是贪心,但这道题的思维量其实挺大的,究竟怎么样移动才能够花费最小呢?首先,一个很简单很简单的性质,每一个0,由它前面的最近的牛移动,如果多头牛,则选择移动最少的那头牛,这样肯定是最优的,不要问我怎么找到的。这可以说是没有问题的。可以说这就是我考试时的思路了,然而。。。你倒是算出来。。。模拟是真的复杂。

    于是我们需要转换思路。其实我们找好一个起点。能将每一头牛排好序,移动就多好啊。是的,这样容易多了。不用讨论这么多,一下就可以算出答案。

    关键就是找起点了。找起点的话最好就要让它这个位置奶牛数量为0或1,这样它是不会动了。而这个环就要顺时针向它来靠近。同时,移动后吧,我们需要留足够的空位给后面的牛,避免要重复移动,那么某一段中国房间数 - 奶牛数越大,这个起点其实越优,找到一个好的起点可以说是十分关键的,我们通过这样的一个起点,许多的贪心思路就可以做出来了,虽然是次正解。

    先看看如何找起点吧:

    for (int i = 1;i <= n ;i ++ ){
            sum += a[i];
            if (sum - i < min_sum){
                min_sum = sum - i;
                pos = i;
            }
        }

    这样,我们找到了从1到pos,由于是一个环,其实从哪一个点开始都一样的。保证这一段,空闲房间最多(在这一段奶牛同时也分别装入房间中时)。这一段空闲房间最多,那么从pos + 1 到 n这一段中,奶牛就最多,房间多半是不够用的(除非原序列每一个房间都刚好一头牛),我们就可以将奶牛往后移,可以说是抢吧,离pos顺时针最近的奶牛最先移,移到空闲的房间。注意一点,我们依旧保证每一个房间都至少有一头奶牛。

    但其实,这里就有一种贪心方法:pos顺时针那一堆是要移动的是吧。我们每一个房间移到1为止,其实找到最近的一个房间,把它占用了,其他牛就不能用,需跳过这一个房间,顺时针找下一个没被占用的房间。这样也可以保证和最小。这种贪心的思路实际上是并没有问题的。那么,也就有了我开始这样的一种次正解

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<iostream>
    #include<vector>
    #include<queue>
    #include<map>
    #include<algorithm>
    #include<cstdlib>
    #define N 100005
    using namespace std;
    int n , a[N * 2] , sum ,pos , min_sum = N * N , fg[N * 2] ;
    long long ans;
    int main(){
        scanf("%d",&n);
        for (int i = 1;i <=n ;i ++ ){
            scanf("%d",&a[i]);
            a[i + n ] = a[i];
        }
        for (int i = 1;i <= n ;i ++ ){
            sum += a[i];
            if (sum - i < min_sum){
                min_sum = sum - i;
                pos = i;
            }
        }
        for (int i = pos; i <= n + pos; i ++ ){
            while (a[i] > 1){
                int j = i + 1;
                while (fg[j] != 0){
                    j ++ ;
                }
                a[i] -- ;
                a[j] ++ ;
                fg[j] = 1;
                ans += (j - i) * (j - i);
            }
        }
        printf("%lld",ans);
    }

    可以说是很神奇了,因为这样的做法。。。3重循环,虽然不是n立方的时间复杂度,但看起来还是挺多的。耗时也应该挺久,其实我们想一想,房间总数只有10万,那么牛的总量亦只有10万,其实细细一想,循环好像并不会耗费长的时间。这是一种近乎暴力的方法,事实证明,求对了起点这道题多好做啊。主要就是可以用大胆的贪心,算花费了。不过,运用这种方法,在四连测最后一次,那道比较类似的题,直接超时。

    起点寻找的这个一个性质其实我是觉得比较难推的,也比较难想。但只有在这样的两段中,才存在一种性质,那便是起点的前面(或后面)一段的房间是肯定够的,多半有剩余,靠后面的牛补上来即可。

    空闲的那一段,牛一般来说是分散的,这样肯定不优,我们让它们靠近起点,后面可能剩下的房间给拥挤的那一段。其实可以说是不好描述的,我觉得这道题贪心方法很多,做出来是这样,解释起来其实就有多种方法了。

    这里我们给每一头牛尝试编号,移动距离也就确定。花费就随之算得出了。那如何弄呢。这里我觉得还有点玄学。

    找了新的起点pos,那么pos后面的那一些数是可以很轻松地移动的,就如我做的性质那般。每一头牛我们都按从1到n的顺序编号,记住它的位置,利用pos,倒着递减,那么每一头牛需移动到哪,我们便算了出来。

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<iostream>
    #include<vector>
    #include<queue>
    #include<map>
    #include<algorithm>
    #include<cstdlib>
    #define N 100005
    using namespace std;
    int n , a[N * 2] , sum ,pos , max_sum = -1,b[N * 2],len , c[N];
    long long ans;
    int main(){
        scanf("%d",&n);
        for (int i = 1;i <=n ;i ++ ){
            scanf("%d",&a[i]);
            int x = a[i];
            while (x){
                c[++len] = i;//求出这是第几头牛,并储存它的位置。
                x -- ;
            }
        }
        for (int i = n;i >= 1 ;i --){//逆着找。可以知道,最大的sum - (n - i + 1)便是最优起点,因为可以证出,这个点前面那一段一定是最小的
            sum += a[i];
            if (sum - (n - i + 1) > max_sum){
                max_sum = sum - (n - i + 1);
                pos = i;
            }
        }
        b[pos] = max_sum + pos;
        int k = b[pos];
        for (int i = n;i >= 2 ;i -- ){//这里倒着,处理每一头牛将要移动到的点
            pos --;
            if (pos == 0)
                pos = n;
            k -- ;
            b[pos] = k;
        }
        for (int i = 1;i <= n ;i ++ ){//直接算出花费,因为每一头牛需要移动到的位置已经可以算出来了。
            if (b[i] >= c[i])
                ans += (b[i] - c[i]) * (b[i] - c[i]);
            else
                ans += (b[i] + n - c[i]) * (b[i] + n - c[i]);
        }
        printf("%lld",ans);
    }

    总结

    总觉得这道题思维量特别大,但实际理解的话,好像也没什么难的。考试的时候,想到无数总贪心方式,事实上都是合理的,但。。。你倒是可以算出花费来。代码复杂度是无法想象的,这道题有很多的性质,其实都可以弄出正解。但关键就要找出那一个是最合适的正解了。主要就要代码好打。

  • 相关阅读:
    poj 2425 AChessGame(博弈)
    poj2975 Nim 胜利的方案数
    hdu 5724 SG+状态压缩
    hdu 5274 Dylans loves tree(LCA + 线段树)
    hdu 5266 pog loves szh III(lca + 线段树)
    hdu 4031 attack 线段树区间更新
    51 nod 1188 最大公约数之和 V2
    51nod 1040 最大公约数之和(欧拉函数)
    51nod 1035:最长的循环节
    Nim游戏(组合游戏Combinatorial Games)
  • 原文地址:https://www.cnblogs.com/lover-fucker/p/13566697.html
Copyright © 2011-2022 走看看