算法 {逐维度扫描网格空间(SOS DP)}
@LOC_3
逐维度扫描网格空间(SOS DP)
定义
SOS: Sum On Subsets
, 他是表示 空间所有维度的大小都是2
的特例, (即整个空间可以用一个二进制ST
来表示 空间所有点都是ST
的子集), 所以这个名字SOS DP
并不是完全表示这个算法, 因为这个算法 可以处理任意的高维度网格空间, 所以名字起成逐维度扫描网格空间 比较好;
朴素做法 即容斥原理 他是M * (2^N)
(令空间点数为M 空间维度为N), 而使用这种算法 时间是M * N
;
#构造网格空间的前缀和#
比如空间维度为`{X,Y,Z}`, 我们要让`Sum`构造成原数组`A`的前缀和数组;
1: Sum = A;
`.` 此时`Sum(x,y,z)`等于`A(x,y,z)`之和;
2: 扫描第一个维度(X), 令`Sum(x,y,z) += Sum(x-1,y,z)`(从小到大);
`.` 此时`Sum(x,y,z)`等于`A(<=x,y,z)`之和;
3: 扫描第二个维度(Y), 令`Sum(x,y,z) += Sum(x,y-1,z)`(从小到大);
`.` 此时`Sum(x,y,z)`等于`A(<=x,<=y,z)`之和;
4: 扫描第三个维度(Z), 令`Sum(x,y,z) += Sum(x,y,z-1)`(从小到大);
`.` 此时`Sum(x,y,z)`等于`A(<=x,<=y,<=z)`之和;
@DELI;
#构造网格空间的差分#
比如空间维度为`{X,Y,Z}`, 我们要让`Diff`构造成原数组`A`的差分数组(因为A是前缀和数组 我们令B为A的原数组 也就是A是B的前缀和数组 即最终我们要让`Diff==B`);
1: Diff = A;
`.` 此时`Diff(x,y,z)`等于`B(<=x,<=y,<=z)`之和;
2: 扫描第一个维度(X), 令`Diff(x,y,z) -= Diff(x-1,y,z)`(从大到小);
`.` 此时`Diff(x,y,z)`等于`B(x,<=y,<=z)`之和;
3: 扫描第二个维度(Y), 令`Diff(x,y,z) -= Diff(x,y-1,z)`(从大到小);
`.` 此时`Diff(x,y,z)`等于`B(x,y,<=z)`之和;
4: 扫描第三个维度(Z), 令`Diff(x,y,z) -= Diff(x,y,z-1)`(从大到小);
`.` 此时`Diff(x,y,z)`等于`B(x,y,z)`之和;
算法
模板
@LINK: (https://editor.csdn.net/md/?articleId=137930238)
;
错误
下面的笔记里面的内容, 有一点是错误的, 即DP转移是错误的;
比如当前在枚举(x,[y], z)
中的y
维度, 下面笔记里的做法 是获取preDP[0,1,2,3,...,y-1]
, 但其实 他可以变成O(1)
即直接获取curDP[y-1]
即可;
笔记
#SOS DP#
定义
SOS DP, 他的用处 是求网格空间的前缀和\差分的一种算法(另一种算法是容斥原理);
构造前缀和, 其实除了容斥原理, 还可以通过逐维度扫描的方式:
空间[X][Y]:
1: Sum = A;
2: 扫描第一维度: FOR( x : [0,X)) FOR( y : [0,Y)) Sum[x][y] += Sum[x-1][y];
3: 扫描第二维度: FOR( x : [0,X)) FOR( y : [0,Y)) Sum[x][y] += Sum[x][y-1];
扫描第一维度后, 此时Sum(x,y)
他的值 是A(<=x, y)
之和;
扫描第二维度后, 此时Sum(x,y)
他的值 是A(<=x, <=y)
之和;
1: Sum Of Subsets
: SOS 子集DP
.
等价于: 网格空间的(可变长K进制)求前缀和/差分;
2: Sum Of Supersets
: SPS 超集DP (@TODO;
.
他应该可以转换为 SOS 子集DP把?
算法
高维空间前缀和
@LINK: @MARK_1
;
@DELI;
#SOS 子集DP/网格空间的前缀和#
@MARK_1
;
定义
当然可以用朴素的容斥原理做法, 时间是...
;
int DP[ Dimens.size()+1][ STs];
{
FOR_( st, 0, STs-1){
auto cor = Get_coordinate(st);
DP[0][st] = A[cor[0]][cor[1]][cor[2]];
}
FOR_( dpI, 1, Dimens.size()){
auto & curDP = DP[ dpI];
auto const& preDP = DP[ dpI-1];
FOR_( dpJ, 0, STs-1){
// Debug_::__IsOpenedDebug = (dpI==2 && dpJ==1);
curDP[ dpJ] = preDP[ dpJ];
auto cor = Get_coordinate( dpJ);
// DE_( curDP[dpJ], cor);
FOR_( p, 0, cor[dpI-1]-1){
auto pp = cor; pp[ dpI-1] = p;
curDP[ dpJ] += preDP[ Get_st(pp)];
}
}
}
}
性质
对于Get_subSpace
, 他俩都一样; 唯一的区别就在于Work()
函数, 即构造前缀和的过程;
算法
可变长K进制
定义
&Dim:
空间各个维度 (比如&Dim=[2,5,4]
, 表示三维空间 所有点范围是([0-1], [0-4], [0-3])
);
耗时为: O( 空间总点数 * 维度 * 维度最大值)
(比如&Dim=[2,5,4]
, 则为O( 2*5*4 * 3 * 5)
);
@TODO
其实状态 没必要用vector<int>
来存, 因为__PointsCount
肯定不会很大 你就用int
来存就可以 (表示变长K进制);
效仿2进制
的做法 Initialize( {2,4,3}, T * val)
, 则val
的范围是[0, 2*4*3)
(没必要写一个INitialze_setValue
);
代码
template< class _SumValueType> class ___PrefixSum_xD_sosDP_kRadix{
//< 给定可变长K进制(也可解释为空间维度), (比如变长K进制为`{2,4,3}` 表示空间是3维 坐标范围为`([0-2),[0-4),[0-3))`, 点`(x,y,z)`的数值为`x*12+y*3+z`); 令`&Subs(x): x的所有子集`(比如`&Subs(120)={120,110,020,100,010,000}` 即`&Subs((x,y,z))为所有([0-x],[0-y],[0-z])的点);
// #代码#: `Initialize()` -> `Initialize_setValue()` -> `Work()`;
public:
std::vector<_SumValueType> __RawArray; // 原始数组;
std::vector<_SumValueType> __Sum_subsets; // `Sum_subsets[(x,y,z)]`: 表示所有`([0-x], [0-y], [0-z])`点的价值和(即和普通数组前缀和一样);
std::vector<int> __Dimensions; // 可变长K进制(也可解释为空间维度);
int __PointsCount; // 空间总点数; 空间所有点 其对应的数值范围是`[0, PointsCount)`
int __GetID( std::vector<int> const& _cor) const{ int ANS = 0; for( int i = 0; i < (int)_cor.size(); ++i){ ANS *= __Dimensions[i]; ANS += _cor[i];} return ANS;}
std::vector<int> __GetCoordinate( int _id) const{ std::vector<int> ANS; for( int i = __Dimensions.size()-1; i >= 0; --i){ ANS.push_back( _id % (__Dimensions[i])); _id /= __Dimensions[i];} std::reverse( ANS.begin(), ANS.end()); return ANS;}
void Initialize( std::vector<int> const& _dimensions){
__Dimensions = _dimensions;
__PointsCount = 1; for( auto i : __Dimensions){ __PointsCount *= i;}
__RawArray.resize( __PointsCount); std::memset( __RawArray.data(), 0, sizeof(__RawArray[0])*__RawArray.size());
}
void Initialize_setValue( std::vector<int> const& _cor, _SumValueType _val){
ASSERT_SYSTEM_( _cor.size() == __Dimensions.size()); for( int i = 0; i < (int)_cor.size(); ++i){ ASSERT_SYSTEM_( _cor[i]>=0 && _cor[i]<__Dimensions[i]);}
__RawArray[ __GetID(_cor)] = _val;
}
void Work(){
__Sum_subsets.resize( __RawArray.size()); std::memcpy( __Sum_subsets.data(), __RawArray.data(), sizeof(__RawArray[0])*__RawArray.size());
for( int dpI = 1; dpI <= (int)__Dimensions.size(); ++dpI){
for( int dpJ = __PointsCount-1; dpJ >= 0; --dpJ){
auto cor = __GetCoordinate( dpJ);
for( int p = 0; p < cor[dpI-1]; ++p){
int raw = cor[dpI-1]; cor[ dpI-1] = p;
__Sum_subsets[ dpJ] += __Sum_subsets[ __GetID(cor)];
cor[ dpI-1] = raw;
}
}
}
}
_SumValueType Get_sum_subSets( std::vector<int> const& _r) const{ // 获取`r`的所有子集的价值和 (比如`r==[1,2,0]`, 则返回值为`{120,110,020,100,010,000}`这些点的价值之和);
ASSERT_SYSTEM_( _r.size() == __Dimensions.size()); for( int i = 0; i < (int)_r.size(); ++i){ ASSERT_SYSTEM_( _r[i]>=0 && _r[i]<__Dimensions[i]);}
return __Sum_subsets[ __GetID(_r)];
}
_SumValueType Get_sum_middleSets( std::vector<int> const& _corL, std::vector<int> const& _corR) const{ // 获取所有介于`[corL, corR]`之间的点的价值之和; (比如`corL=[1,2,0], corR=[2,4,0]` 则表示`{120|130|140|220|230|240}`这些点的价值之和;
ASSERT_SYSTEM_( _corL.size()==_corR.size() && (_corL.size()==__Dimensions.size()));
for( int i = 0; i < (int)_corL.size(); ++i){ ASSERT_SYSTEM_( _corL[i]<=_corR[i]);}
_SumValueType ANS = 0;
auto cor = _corR;
for( int mask = 0; mask < (1<<__Dimensions.size()); ++mask){
for( auto t = mask; t != 0;){
auto bit = __builtin_ctz( t); t ^= (1<<bit);
cor[ bit] = _corL[bit] - 1;
}
bool valid = 1;
for( auto i : cor){ if( i < 0){ valid=0; break;}}
if( valid){
if( __builtin_popcount(mask) & 1){ ANS -= __Sum_subsets[ __GetID(cor)];}
else{ ANS += __Sum_subsets[ __GetID(cor)];}
}
for( auto t = mask; t != 0;){
auto bit = __builtin_ctz( t); t ^= (1<<bit);
cor[ bit] = _corR[bit];
}
}
return ANS;
}
}; // class ___PrefixSum_xD_sosDP_kRadix
2进制
@MARK(0)
;
定义
这是可变长K进制的特例, 即当K==2
的特例; 代码中的__Bits
他, 就对应变长K进制里的 空间维度;
.
比如__Bits = 3
, 他对应可变长K进制算法里的空间维度为{2,2,2}
;
时间为2^N * N
(N为二进制长度);
性质
代码
template< class _Type_> struct ___PrefixSum_xD_sosDP_binary{
std::vector<_Type_> PrefixSum; // `PrefixSum[101] = _rawArray[000|001|100|101]之和`;
int __Bits; // 空间的维度; 比如`Bits=3` 表示空间所有点是`([0-1],[0-1],[0-1])`;
void Initialize( _Type_ const* _rawArray, int _bits){ // `_rawArray`的范围是`[0, (1<<bits))`;
__Bits = _bits;
PrefixSum.resize( 1<<_bits); std::memcpy( PrefixSum.data(), _rawArray, sizeof(PrefixSum[0])*PrefixSum.size());
for( int dpI = 1; dpI <= __Bits; ++dpI){ // SOS_DP (`Sum_subsets`其实是`[Bits][1<<Bits]`的DP 只不过把`[Bits]`给数组压缩了);
for( auto dpJ = (1<<__Bits)-1; dpJ >= 0; --dpJ){
if( (dpJ>>(dpI-1)) & 1){ PrefixSum[ dpJ] += PrefixSum[ dpJ^(1<<(dpI-1))];}
}
}
}
_Type_ Get_sum_middleSets( int _l, int _r) const{ // `&S: [l的超集]&&[r的子集]` 返回值为`RawArray[&{S}]`之和; (比如`l=010, r=110`, 则`&{S}=={010,110}`);
ASSERT_SYSTEM_( (_l&((1<<__Bits)-1)==_l) && (_r&((1<<__Bits)-1)==_r) && (_l<=_r));
ASSERT_SYSTEM_( _l != 0); // 请直接调用`Sum_subsets[_r]`;
_Type_ ANS = 0;
for( int mask = 0; mask < (1<<__Bits); ++mask){
int cur = _r;
bool valid = 1;
for( auto t = mask; t != 0;){
auto bit = __builtin_ctz( t); t ^= (1<<bit);
if( (_l>>bit) & 1){ Integer_::Binary_::SetBit( cur, bit, 0);}
else{ valid = 0; break;}
}
if( valid){
if( __builtin_popcount(mask) & 1){ ANS -= PrefixSum[ cur];}
else{ ANS += PrefixSum[ cur];}
}
}
return ANS;
}
}; // class ___PrefixSum_xD_sosDP_binary
应用
@LINK: https://editor.csdn.net/md/?articleId=137920019
;
@DELI;
#SOS 子集DP/网格空间的差分#
定义
已知&Dim:
空间维度; 已知&Sum:
&ANS
的前缀和数组; 求&ANS:
原始数组;
.
比如&Dim=[2,5,4]
, 则&Sum[ (0,1,1)] = &ANS[ (0,1,1)] + &ANS[ (0,1,0)] + &ANS[ (0,0,1)] + &ANS[ (0,0,0)]
;
当然可以用朴素的空间差分(容斥原理), 但时间是2^N * 2^N
(N为空间维度);
而使用SOS_DP, 时间为O(2^N * M)
(M为MAX( {&Dim[i]} )
);
不从算法层面来证明了(应该很复杂…), 而是代码层面来推导; 因为&Sum
是可以通过&ANS
做SOS_DP来构造的, 所以 那么我们对&Sum
做逆向的 SOS_DP 不就可以还原出原始数组&ANS
啦;
算法
可变长K进制
@TODO;
2进制
@MARK_2
;
定义
时间是O( 2^N * N)
; (N为空间维度 即二进制长度);
代码
template< class _Type_> struct ___Differential_xD_sosDP_binary{
std::vector<_Type_> Differential; // `_rawArray[101] = Differential[000|001|100|101]之和`;
int __Bits; // 空间的维度; 比如`Bits=3` 表示空间所有点是`([0-1],[0-1],[0-1])`;
void Initialize( _Type_ const* _rawArray, int _bits){ // `_rawArray`的范围是`[0, (1<<bits))`;
__Bits = _bits;
Differential.resize( 1<<_bits); std::memcpy( Differential.data(), _rawArray, sizeof(Differential[0])*Differential.size());
for( int dpI = 1; dpI <= __Bits; ++dpI){ // SOS_DP (`Sum_subsets`其实是`[Bits][1<<Bits]`的DP 只不过把`[Bits]`给数组压缩了);
for( auto dpJ = (1<<__Bits)-1; dpJ >= 0; --dpJ){
if( (dpJ>>(dpI-1)) & 1){ Differential[ dpJ] -= Differential[ dpJ^(1<<(dpI-1))];}
}
}
}
void Modify_middleSets( int _l, int _r, _Type_ _add) const{ // `&S: [l的超集]&&[r的子集]`; `&Raw: Differential对应的前缀和数组`; 我们会修改`&Raw[ &S] += _add`; (比如`l=010, r=110`, 则`&S=={010,110}`);
ASSERT_SYSTEM_( (_l&((1<<__Bits)-1)==_l) && (_r&((1<<__Bits)-1)==_r) && (_l<=_r));
ASSERT_SYSTEM_( _l != 0); // 请直接调用`Sum_subsets[_r]`;
TODO_RunTime_( "not finished");
for( int mask = 0; mask < (1<<__Bits); ++mask){
int cur = _r;
bool valid = 1;
for( auto t = mask; t != 0;){
auto bit = __builtin_ctz( t); t ^= (1<<bit);
if( (_l>>bit) & 1){ Integer_::Binary_::SetBit( cur, bit, 0);}
else{ valid = 0; break;}
}
if( valid){
if( __builtin_popcount(mask) & 1){}
else{ }
}
}
}
}; // class ___Differential_xD_sosDP_binary
应用
@LINK: https://editor.csdn.net/md/?articleId=137920019
;