我复制了图片,样例什么的就不要在意了。
解题思路
也许有人很迷惑,这个样例怎么算出来的,至少都是上百啊,我身边是有人跟我这样说过的,确实,题目描述有一点问题。它说的是“每头奶牛都待在一个房间”,这句话是有歧义的。它没说是都在“一个房间”,还是均匀地在“每一个房间”。这道题呢,是均匀地在每一个房间,运用一下贪心,模拟什么的,绝对是可以弄出样例的。
其实,这道题的实际就是贪心,但这道题的思维量其实挺大的,究竟怎么样移动才能够花费最小呢?首先,一个很简单很简单的性质,每一个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);
}
总结
总觉得这道题思维量特别大,但实际理解的话,好像也没什么难的。考试的时候,想到无数总贪心方式,事实上都是合理的,但。。。你倒是可以算出花费来。代码复杂度是无法想象的,这道题有很多的性质,其实都可以弄出正解。但关键就要找出那一个是最合适的正解了。主要就要代码好打。