算法 {逐维度扫描网格空间(SOS DP)}

算法 {逐维度扫描网格空间(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;

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值