问题描述
|
给定一个由n个类型为整数的元素组成的向量,哪种方法能产生最少的转换步骤,从而使所有元素均相等的向量更有效?
在一个步骤中,您最多可以将一个元素从一个元素转移到其相邻元素([0,3,0]-> [1、2、0]可以,但是[0,3,0]-> [1, 1,1])。
在一个步骤中,一个元素可以收到2个点:一个来自其左侧邻居,一个来自右侧([3,0,3]-> [2,2,2])。
first元素和last元素只有一个邻居,分别是2nd元素和n-1元素。
一个元素在任何步骤都不能为负。
例子 :
Given :
0,3,0
Then 2 steps are required :
1,2,0
1,1,1
Given :
3,3
Then 1 step is required :
2,2
Given :
4,4,0
Then 3 steps are required :
3,0
2,1; 1,1
我当前的算法基于元素每一侧的整数之和。但是我不确定它是否能产生最少的步骤。
仅供参考,问题是代码竞赛(由Criteo http://codeofduty.criteo.com创建)的一部分,已经结束。
解决方法
这是一种方法。您知道数组的总和,因此知道每个单元格中的目标数。
因此,您还知道每个子数组的目标总和。
然后遍历数组,并在每个步骤上进行设计:
向左移动1:如果与前一个元素的和小于期望值。
向右移动1:如果当前元素的总和超过期望值
什么也不要做:如果以上两项都为假
重复此操作,直到没有更多更改为止(即,您仅对每个元素应用了3)。
public static int F(int[] ar)
{
int iter = -1;
bool finished = false;
int total = ar.Sum();
if (ar.Length == 0 || total % ar.Length != 0) return 0; //can\'t do it
int target = total / ar.Length;
int sum = 0;
while (!finished)
{
iter++;
finished = true;
bool canMoveNext = true;
//first element
if (ar[0] > target)
{
finished = false;
ar[0]--;
ar[1]++;
canMoveNext = ar[1] != 1;
}
sum = ar[0];
for (int i = 1; i < ar.Length; i++)
{
if (!canMoveNext)
{
canMoveNext = true;
sum += ar[i];
continue;
}
if (sum < i * target && ar[i] > 0)
{
finished = false;
ar[i]--;
ar[i - 1]++;
sum++;
}
else if (sum + ar[i] > (i + 1) * target && ar[i] > 0) //this can\'t happen for the last element so we are safe
{
finished = false;
ar[i]--;
ar[i + 1]++;
canMoveNext = ar[i + 1] != 1;
}
sum += ar[i];
}
}
return iter;
}
, 我有一个主意。我不确定会产生最佳结果,但感觉可以。
假设初始向量是N大小的向量V
。您需要另外两个N大小的向量:
在L
向量中,从左开始对元素求和:L[n] = sum(i=0;i<=n) V[n]
在R
向量中,从右开始对元素求和:R[n] = sum(i=n;i<N) V[n]
最后,您需要一个最后的特定值:V的所有元素之和应等于k*N
,其中k
是一个整数。你有L[N-1] == R[0] == k*N
让我们取L
矢量。这个想法是,对于任何n,考虑V
向量分为两部分,一个从0到n,另一个包含其余部分。如果为L[n]<n*k
,则必须用第二部分的值“填充”第一部分。反之亦然,如果L[n]>n*k
。如果L[i]==i*k
,那么恭喜您,问题可以细分为两个子问题!没有理由将第二个向量中的任何值都转移到第一个向量中,反之亦然。
然后,该算法很简单:对于n的每个值,检查L[n]-n*k
和R[n]-(N-n)*k
的值并采取相应的措施。只有一种特殊情况,如果L[n]-n*k>0
和R[n]-(N-n)*k>0
(V [n]处有一个高值),则必须在两个方向上将其清空。只需随机选择一个转移方向即可。
当然,不要忘记相应地更新L
和R
。
编辑:实际上,看来您只需要L
向量。这是一个简化的算法。
如果L[n]==n*k
,什么都不做
如果L[n]<n*k
,则将一个值从V[n+1]
转移到V[n]
(如果V[n+1]
> 0当然是)
如果L[n]>n*k
,则将一个值从V[n]
转移到V[n+1]
(如果V[n]
> 0当然是)
并且(特殊情况下),如果您被要求从V[n]
转移到V[n-1]
和V[n+1]
,只需随机转移一次,它不会改变最终结果。
, 感谢Sam Hocevar,为fiver的以下替代实现提供了以下方法:
public static int F(int[] ar)
{
int total = ar.Sum();
if (ar.Length == 0 || total % ar.Length != 0) return 0; //can\'t do it
int target = total / ar.Length;
int[] left = new int[ar.Length];
int[] right = new int[ar.Length];
int maxshifts = 0;
int delta = 0;
for (int i = 0; i < ar.Length; i++)
{
left[i] = delta < 0 ? -delta : 0;
delta += ar[i] - target;
right[i] = delta > 0 ? delta : 0;
if (left[i] + right[i] > maxshifts) {
maxshifts = left[i] + right[i];
}
}
for (int iter = 0; iter < maxshifts; iter++)
{
int lastleftadd = -1;
for (int i = 0; i < ar.Length; i++)
{
if (left[i] != 0 && ar[i] != 0)
{
ar[i]--;
ar[i - 1]++;
left[i]--;
}
else if (right[i] != 0 && ar[i] != 0
&& (ar[i] != 1 || lastleftadd != i))
{
ar[i]--;
ar[i + 1]++;
lastleftadd = i + 1;
right[i]--;
}
}
}
return maxshifts;
}