我正在构建一个包含Partitions类的C ++库。我试图实施共轭(下面解释),我无法让它工作。
我的班级成员是:
size_t _size;
size_t _length;
std::vector<int> _parts;
例如,整数分区[5,4,4,1]
具有
_size = 14 // 5 + 4 + 4 + 1
_length = 4 // 4 nonzero parts
_parts[0] = 5
_parts[1] = 4
_parts[2] = 4
_parts[3] = 1
_parts[i] = junk // i>3
如果分区为[m_1,m_2,...,m_k]
,则共轭为[n_1,n_2,...,n_l]
其中
l = m_1 // length and the first part are switched
n_i = sum{ m_j | m_j > i}
例如,[5,4,4,1]
的共轭为[4,3,3,3,1]
。另一种看到这种情况的方法是将分区绘制为单位正方形行,其中i
行中的正方形数为m_i
。读取列的高度然后给出共轭。对于同一个例子,图片是
1| x
4| x x x x
4| x x x x
5| x x x x x
__________
4 3 3 3 1
数学将编程语法翻译为m_i = _parts[i-1]
和k = _length
。这是一个破坏的共轭实现:
void
Partition::conjugate() {
size_t k = _length;
_length = _parts[0];
int newPart;
for (int i=(int)_length; i>0; --i) {
newPart = 0;
for (int j=0; j<k; ++j) {
if (_parts[j] >= i) newPart++;
else break;
}
_parts[i-1] = newPart;
}
}
这在大部分时间都有效,但偶尔会覆盖仍然需要的部分分区。我正在寻找一种巧妙的方法来进行结合,即不创建Partition
的新实例。
另一种考虑共轭的方法是认识到共轭是以下序列
k...k (k-1)...(k-1) ... 1...1
x m_k x(m_(k-1)-m_k) x(m_1 - m_2)
使用这个想法,我有以下实现给出了正确的答案:
void
Partition::conjugate() {
if (_length == _size) {
this->first();
return;
} else if (_length == 1) {
this->last();
return;
}
std::vector<int> diffs;
diffs.push_back(_parts[_length-1]);
for (size_t i=_length-1; i>0; --i)
diffs.push_back(_parts[i-1]-_parts[i]);
size_t pos = 0;
for (int i=0; i<_length; ++i) {
for (int j = diffs[i]; j>0; --j)
_parts[pos++] = (int)_length - i;
}
_length = pos;
}
然而,它使用另一个std向量,我试图避免。
根据Evgeny Kluev的回答(下面接受),这里有最终的代码(详见他的回答):
void
Partition::conjugate() {
if (_length == _size) {
this->first();
return;
} else if (_length == 1) {
this->last();
return;
}
int last = _parts[_length-1];
for (int i=1; i<_length; ++i)
_parts[_size-i] = _parts[i-1] - _parts[i];
size_t pos = 0;
for (int i=0; i<last; ++i)
_parts[pos++] = (int)_length;
for (int i=1; i<_length; ++i) {
for (int j = _parts[_size-_length+i]; j>0; --j)
_parts[pos++] = (int)_length - i;
}
_length = pos;
}
答案 0 :(得分:2)
这可以通过3次线性传递完成:
这是C ++ 11实现(另请参阅complete program on Ideone)。
void conjugate()
{
size_t space = 0;
for (size_t i = 0; i < _length; ++i)
space = max(space, _parts[i] + i);
++space;
_parts.resize(space);
reverse(begin(_parts), end(_parts));
auto it_out = begin(_parts);
auto it_in = end(_parts) - _length;
size_t prev = 0;
for (; it_in < end(_parts); ++it_in)
{
it_out = fill_n(it_out, *it_in - prev, end(_parts) - it_in);
prev = *it_in;
}
_length = it_out - begin(_parts);
_parts.resize(_length);
}
此实现在某种意义上就位。这意味着它使用单个矢量并最小化共轭所需的额外空间。在某些情况下(如{4,1,1,1}或{4,3,2,1}),向量中只添加一个额外元素。在困难的情况下(如{4,4,4,4}),矢量的大小暂时加倍。
可以在不使用太多额外空间的情况下使用此方法。因为&#34;坏&#34;像{4,4,4,4}这样的情况显然具有非常低的熵,我们可以压缩原始分区。但是这会使代码复杂化。
RLE和delta编码的组合使得该算法真正就地(这意味着O(1)额外的空间)。使用正数(或零高位)来编码原始分区中相邻值之间的差异(因为共轭步骤无论如何只需要差异)。使用负数(或非零高位)编码零运行(数字的剩余位表示多少个零)。所有这些限制delta值和零计数器到范围的一半。但在这两种情况下,最多可能有一个值超过范围的一半。所以我们可以在这个超大值前加上零(在向量中保留空间,最多2个这样的零)。
答案 1 :(得分:1)
这个答案描述了一种算法,由于它使用了符号位,因此可以说是不合适的。由于您使用的是签名类型,我还是会发布它。
每个分区都由其最大值唯一定义。对于您的示例分区
4 | x
3 | x x x x
2 | x x x x
1 | x x x x x
- - - - -
1 2 3 4 5
我用坐标而不是分区的内部表示及其转置来标记轴,最大值是(1,4),(4,3)和(5,1)。
算法的第一步/最后一步是转换为描述最大值的表示形式。一般来说,我们只有一个坐标的空间,所以我们必须使用向量中的位置来描述另一个坐标。正向转换只是将每个数字的最后一次出现替换为0。
5 4 4 1 -> 5 0 4 1
4 3 3 3 1 -> 4 0 0 3 1
- - - - -
1 2 3 4 5
前向和后向转换都很容易实现。
中间步骤是对此替代表示进行转置。扫描数组,查找正整数。当我们找到一个x
在位置y
时,写一个0
到位置y
并将-y
写到位置x
。实际上生活比这复杂一点;如果位置x
包含正整数,那么我们需要将其存储在临时中,然后再处理它。最后,扫描数组,将-x
替换为x
(显然,这不需要单独执行)。总之,我们正在进行排列,并且可以记住特定项目是否已移动。
答案 2 :(得分:1)
输入数组in[] = [1,4,4,5]
,假设为in[i]>=i+1
(如果不是,则为每个元素添加x)。
然后我们设置out[] = [1,4,4,5,0]
(out[i]=in[i]
);
我们从结尾扫描out[]
,找到第一个非零元素,将其存储为p
,p
表示我们将解析out[p]
计算元素出现的数量,我们将得到结果。
[1,4,4,5,0] , p = 3, cnt = 1 (parse out[3] = 5)
->
[1,4,4,5,1] , p = 2, cnt = 3 (parse 4)
->
[1,3,3,3,1] , p = 0, cnt = 3 (parse 1)
->
[4,3,3,3,1] , p = -1, cnt = 4 (done)
当我们解析out[i]
(即in[i]
)时,我们将根据我们的假设指定next_parse_element ~last_element_assigned-1
,next_element_index + 1~last element assigned-1
,但是next_element_index + 1~last element assigned
我们的程序解析并且无用。
示例([1,4,4,5]):当我们解析4
时,下一个元素是1
,因此我们应该计算out[1](next parse element) ~ out[3](last_assigned - 1)
。但是你看,out[1] - out[3]
没用,我们也不会丢失任何信息。
如果与假设不匹配,我们可以将x添加到每个元素(in[i]+=x
)以匹配假设(最多添加in
大小,仍为O(n)
空间)。然后我们按照上述方法进行操作,忽略x
开头的out[]
元素,它就是输入的真实答案。
add 1 to every element in int[]
4 | x x
3 | x x x x x
2 | x x x x x
1 | x x x x x x
- - - - - -
1 2 3 4 5 6
它[4,4,3,3,3,1]
,忽略第一个元素,这是真正的答案。
时间复杂性。如果in[]
未排序,则为O(nlogn)
,或O(n)
。
详细信息请参阅我的代码:
import java.util.Arrays;
public class Conjugate {
int[] part;
public Conjugate(int[] array) {
Arrays.sort(array);
int max = 0;
for (int i = 0; i < array.length; i++)
{
max = Math.max(max, array[i]);
}
part = new int[Math.max(max,array.length)];
for (int i = 0;i < array.length; i++)
{
part[i] = array[i];
}
int cnt = 0, p = part.length - 1, next = 0;
for (int i = array.length-1; i >= 0; i--)
{
for (int j = i; j>=0&&part[i]==part[j]; j--)
{
cnt ++;
if (j - 1 < 0)
next = 0;
else
next = part[j-1];
i=j;
}
for (int j = p; j >= next ; j--)
{
part[j] = cnt;
}
p = next - 1 ;
}
}
void output()
{
for (int i = 0; i < part.length; i++)
{
System.out.print(part[i] + " ");
}
}
public static void main(String[] args) {
int[] a = {1,4,4,5};
Conjugate con = new Conjugate(a);
con.output();
}
}