• 蓝书 0x01 位运算


    0x01 位运算

    补码

    memset只能赋值出“每8位都相同”的int

    0x7F7F7F7F是memset能初始化出的最大数组,但需要将一个数组中的数组初始化为正无穷时,为了避免加法运算上溢或者繁琐判断,一般用0x3F3F3F3F

    移位运算

    1 < < n = 2 n 1<1<<n=2n

    “整数/2”在c++实现为“除以2向零取整”,比如(-3)/2=-1

    AcWing 89. a^b

    a b m o d    p a^b\mod p abmodp

    0 ≤ a , b ≤ 1 0 9 0≤a,b≤10^9 0a,b109
    1 ≤ p ≤ 1 0 9 1≤p≤10^9 1p109

    b = c k − 1 ∗ 2 k − 1 + c k − 2 ∗ 2 k − 2 + . . . + c 0 ∗ 2 0 b=c_{k-1}*2^{k-1}+c_{k-2}*2^{k-2}+...+c_0*2^0 b=ck12k1+ck22k2+...+c020

    a b = a c k − 1 ∗ 2 k − 1 ∗ a c k − 2 ∗ 2 k − 2 ∗ . . . ∗ a c 0 ∗ 2 0 a^b=a^{c_{k-1}*2^{k-1}}*a^{c_{k-2}*2^{k-2}}*...*a^{c_0*2^0} ab=ack12k1ack22k2...ac020

    a 2 i = a 2 i − 1 ∗ 2 = ( a 2 i − 1 ) 2 a^{2^i}=a^{2^{i-1}*2}=(a^{2^{i-1}})^2 a2i=a2i12=(a2i1)2因此遍历b的每一位,如果当前这位是1,说明要乘上这个系数,而这个系数是随着遍历b的每一位,每次变成自己的平方

    • 快速幂时间复杂度为 O ( l o g 2 b ) O(log_2b) O(log2b)
    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    
    int qmi(int a, int b, int p) {
        int ans = 1 % p;
        while (b) {
            if (b & 1) ans = (ll)ans * a % p;
            a = (ll)a * a % p;
            b >>= 1;
        }
        return ans;
    }
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        int a, b, p;
        cin >> a >> b >> p;
        cout << qmi(a, b, p);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 快速幂
    • 注意 ll ans = 1 % p,初始化ans也要取模,否则可能b为0且p为1时,答案错误
    • 如果参数都写int,记得在乘法运算的时候第一个乘法参数前面加上强制转化成long long

    AcWing 90. 64位整数乘法

    1 ≤ a , b , p ≤ 1 0 18 1≤a,b,p≤10^{18} 1a,b,p1018

    将b化为二进制表示后, a ∗ b = c k − 1 ∗ a ∗ 2 k − 1 + c k − 2 ∗ a ∗ 2 k − 2 + . . . + c 0 ∗ a ∗ 2 0 a*b=c_{k-1}*a*2^{k-1}+c_{k-2}*a*2^{k-2}+...+c_0*a*2^0 ab=ck1a2k1+ck2a2k2+...+c0a20

    又因为 a ∗ 2 i = ( a ∗ 2 i − 1 ) ∗ 2 a*2^i=(a*2^{i-1})*2 a2i=(a2i1)2

    发现运算过程中每一步的结果都不超过 2 ∗ 1 0 18 2*10^{18} 21018,仍然在64位整数longlong的表示范围内

    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        ll a, b, p;
        cin >> a >> b >> p;
        ll ans = 0;
        while (b) {
            if (b & 1) ans = (ans + a) % p;
            a = a * 2 % p;
            b >>= 1;
        }
        cout << ans;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    二进制状态压缩

    • ( 1 < < i ) = 2 i = (1<(1<<i)=2i= i i i位是1 = = = 1后面跟着 i i i个0
    • 将n的第k位取反:n xor (1 << k)(结论:1、异或0不会产生变化;2、异或1会取反)
    • 将n的第k位赋值1: n | (1 << k)
    • 将n的第k位赋值0:n & (~(1 << k))(注意如何由1表示0,取反)
    • 取出n的后k位(第0~k-1位):n & ((1 << k) - 1)(让前面的位都是0,因此&0,让后面的位原先是1的为1,原先是0的为0,因此&1)
    • m位二进制整数,当m不太大时,可以直接使用一个整数类型存出;当m较大时,可以用若干个整数类型(int数组),也可以直接用stl中的bitset(第0x71节)
    • ans的第bit位为res0:ans += (res0 << bit)(不需要分类讨论为res0是0还是1)

    AcWing 91. 最短Hamilton路径

    题意:

    • 给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。
    • Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。
    • 1 ≤ n ≤ 20 1≤n≤20 1n20

    思路:

    • 朴素做法:枚举n个点的全排列,计算路径长度取最小值, O ( n ∗ n ! ) O(n*n!) O(nn!)
    • 二进制状态压缩DP O ( n 2 ∗ 2 n ) O(n^2*2^n) O(n22n)
    • 在任意时刻表示哪些点已经被经过,哪些点没有被经过:用一个n位二进制数;在任意时刻还需要知道当前所处的位置,因此, F [ i , j ] ( 0 ≤ i < 2 n , 0 ≤ j < n ) F[i,j](0 \leq i < 2^n, 0 \leq j < n) F[i,j](0i<2n,0j<n)表示点被经过的状态对应的二进制数为i,且目前处于点j时的最短路径
    • 在起点时,有 F [ 1 , 0 ] = 0 F[1,0]=0 F[1,0]=0,将F数组其他的值设为无穷大;最终目标是 F [ ( 1 < < k ) − 1. n − 1 ] F[(1 << k)-1.n-1] F[(1<<k)1.n1]
    • 在任何时刻,有 F [ i , j ] = m i n ( F [ i F[i,j]=min(F[i F[i,j]=min(F[i xor ( 1 < < j ) , k ] + w e i g h t ( k , j ) ) (1 << j), k]+ weight(k,j)) (1<<j),k]+weight(k,j)),其中, 0 ≤ k < n 0 \leq k < n 0k<n并且 ( ( i > > j ) ((i >> j) ((i>>j) $ 1 ) = 1 1)=1 1)=1,因为每个点只能被恰好经过一次,所以在上一时刻“被经过的点的状态”对应的二进制数的第j位应该赋值为0
    #include 
    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    const int N = 20, M = 1 << 20;
    
    int n;
    int w[N][N];
    int dp[M][N];
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        cin >> n;
        for (int i = 0; i < n; ++ i)
            for (int j = 0; j < n; ++ j)
                cin >> w[i][j];
        
        memset(dp, 0x3f, sizeof dp);
        dp[1][0] = 0;
        for (int i = 1; i < (1 << n); ++ i) {
            for (int j = 0; j < n; ++ j) {
                if (i >> j & 1) {
                    for (int k = 0; k < n; ++ k) {
                        if ((i - (1 << j)) >> k & 1) {
                            dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + w[k][j]);
                        }
                    }
                }
            }
        }
        cout << dp[(1 << n) - 1][n - 1];
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    AcWing 998. 起床困难综合症

    题意 :

    • 选择[0, m]之间的一个整数 x 0 x_0 x0,经过给定的n次位运算,使结果ans最大
    • n ≤ 1 0 5 , m ≤ 1 0 9 n \leq 10^5,m \leq 10^9 n105,m109

    思路 :

    • 位运算的主要特点之一:在二进制表示下不进位;我们可以从高位(因为要最大)到低位,依次考虑 x 0 x_0 x0的每一位填0还是填1
    • x 0 x_0 x0的第k位应该填1,当且仅当同时满足下列两个条件:
      1、已经填好的更高位构成的数值加上 1 << k 以后不会超过m
      2、用每个参数的第k位参与位运算,
    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    typedef pair<string, int> psi;
    const int N = 1e5 + 10;
    
    int n, m;
    psi a[N];
    
    int calc(int bit, int now) {
        for (int i = 1; i <= n; ++ i) {
            string op = a[i].first;
            int x = (a[i].second >> bit & 1);
            if (op == "AND") now &= x;
            else if (op == "OR") now |= x;
            else now ^= x;
        }
        return now;
    }
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        cin >> n >> m;
        for (int i = 1; i <= n; ++ i) cin >> a[i].first >> a[i].second;
        int ans = 0, val = 0;
        for (int bit = 29; bit >= 0; -- bit) {
            int res0 = calc(bit, 0);
            int res1 = calc(bit, 1);
            if (val + (1 << bit) <= m && res1 > res0) {
                val += (1 << bit);
                ans += (res1 << bit);
            } else {
                ans += (res0 << bit);
            }
        }
        cout << ans;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    成对变换

    • 对于非负整数n:
      当n为偶数时,n xor 1 等于 n + 1
      当n为奇数时,n xor 1 等于 n - 1
    • 这个性质经常用于图论 邻接表 中边集的存出,无向边(双向边)图中,一对正反方向的边(在第0x13节讲

    lowbit运算

    lowbit(n)定义为 非负整数n(即 n > 0 n>0 n>0) 在二进制表示下“最低位的1及后边所有的0”所构成的数值

    结论:lowbit(n) = n & (~n + 1) = n & (-n)

    推导:
    设n>0,n的第k位为1,第0~k-1为都是0
    对n取反加一后,n的第k+1位到最高位恰好与原来相反,第0~k-1位仍然为0;即仅有第k位为1,其余位都是0;然后用原先的n & 取反加一后的n
    在补码表示下,~n = -1 - n

    找出整数二进制表示下所有是1的位(所花费时间与1的个数同级)(lowbit配合Hash):不断把n赋值位n - lowbit(n),直至n = 0 :

    • c++math库的log函数以e为底,且复杂度常数较大,因此我们预处理一个数组,通过hash方法代替log运算;当n较小(比如20)时,令 H [ 2 k ] = k H[2^k]=k H[2k]=k
    • 效率更高的方法:建立一个长度为37的数组H,令 H [ 2 k m o d    37 ] = k H[2^k\mod 37]=k H[2kmod37]=k;使用的数学思想:
    • ∀ k ∈ [ 0 , 35 ] , 2 k m o d    37 \forall k\in [0,35],2^k\mod37 k[0,35],2kmod37互不相等,且恰好取遍整数1~36
    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    
    int H[37];
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        for (int i = 0; i < 36; ++ i) H[(1ll << i) % 37] = i;
        int n;
        while (cin >> n) {
            while (n > 0) {
                cout << H[(n & -n) % 37] << ' ';
                n -= (n & -n);
            }
            cout << endl;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    lowbit运算也是树状数组(0x42)中的一个基本运算

    0x02 递推与递归

    递推与递归的宏观描述

    以已知的“问题边界”为起点向“原问题”正向推到的扩展方式就是 递推

    递推与递归的简单应用

    AcWing 92. 递归实现指数型枚举

    题意 :

    • 从 1∼n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

    思路 :

    • 所有可能的方案总数共有 2 n 种 2^n种 2n
    void dfs(int u, int state) {
        if (u == n) {
            for (int i = 0; i < n; ++ i) {
                if (state >> i & 1)
                    cout << i + 1 << ' ';
            }
            cout << endl;
            return ;
        }
        dfs(u + 1, state + (1 << u));
        dfs(u + 1, state);
    }
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        cin >> n;
        dfs(0, 0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    AcWing 93. 递归实现组合型枚举

    题意 :

    • 从 1∼n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。

    思路 :

    • 剪枝后时间复杂度从 2 n 2^n 2n降为 C n m C^m_n Cnm,剪枝:表示已选元素容器的vector如果size大于m或者加上剩下所有的数仍然小于m,则return剪枝
    • 或者,dfs三个参数,「从第u个开始选」、「当前已经选了s个」,「当前所有元素被选的状态」;dfs返回条件有两个,一个是选的个数已经有m个了,一个是没有元素可以供选择
    int n, m;
    
    void dfs(int u, int s, int state) {
        if (s == m) {
            for (int i = 0; i < n; ++ i)
                if (state >> i & 1)
                    cout << i + 1 << ' ';
            cout << endl;
            return ;
        }
        if (u == n) return ;
        
        for (int i = u; i < n; ++ i) {
            dfs(i + 1, s + 1, state + (1 << i));
        }
    }
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        cin >> n >> m;
        dfs(0, 0, 0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    AcWing 94. 递归实现排列型枚举

    题意 :

    • 把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

    思路 :

    • 所有可能的方案总数有 n ! n! n!
    • 用一个int类型的state表示当前每个元素被选的状态,再用一个vector容器记录顺序;两个参数:「第几个盒子」、「所有点被选的状态」
    int n;
    vector<int> path;
    
    void dfs(int u, int state) {
        if (u == n) {
            for (int i = 0; i < path.size(); ++ i)
                cout << path[i] + 1 << ' ';
            cout << endl;
            return ;
        }
        for (int i = 0; i < n; ++ i) {
            if (!(state >> i & 1)) {
                path.push_back(i);
                dfs(u + 1, state + (1 << i));
                path.pop_back();
            }
        }
    }
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        cin >> n;
        dfs(0, 0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    AcWing 95. 费解的开关

    题意 :

    • 25 盏灯排成一个 5×5 的方形。每一个灯都有一个开关,游戏者可以改变它的状态。每一步,游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。给定一些游戏的初始状态,编写程序判断游戏者是否可能在 6 步以内使所有的灯都变亮。一共输出 n 行数据,每行有一个小于等于 6 的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。对于某一个游戏初始状态,若 6 步以内无法使所有灯变亮,则输出 −1。

    思路 :

    • 我们发现三个性质:1、每个位置最多点击一次;2、点击的顺序不影响结果;3、固定第一行,也就是第一行不再被改变后,则满足题意的点击方案最多只有一种
    • 因此,我们枚举第一行的点击方案( 5 2 = 25 5^2=25 52=25),再考虑第2~5行如何点击,如果到达最后一行不全为0,说明这种点击方案不合法,最少点击次数就是所有合法方案中点击次数的最小值
    • 注意这里backup和g数组不要开5和5,不知道为什么爆了
    #include 
    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    
    char g[10][10];
    int dx[5] = {-1, 1, 0, 0, 0};
    int dy[5] = {0, 0, 0, 1, -1};
    
    void turn(int x, int y) {
        for (int i = 0; i < 5; ++ i) {
            int a = x + dx[i], b = y + dy[i];
            if (a >= 0 && a < 5 && b >= 0 && b < 5) {
                if (g[a][b] == '0') g[a][b] = '1';
                else g[a][b] = '0';
            }
        }
    }
    int solve() {
        int ans = 1e9;
        for (int k = 0; k < (1 << 5); ++ k) {
            char backup[10][10];
            memcpy(backup, g, sizeof g);
            int res = 0;
            
            for (int i = 0; i < 5; ++ i) {
                if (k >> i & 1) {
                    res ++ ;
                    turn(0, i);
                }
            }
            for (int i = 0; i < 4; ++ i) {
                for (int j = 0; j < 5; ++ j) {
                    if (g[i][j] == '0') {
                        res ++ ;
                        turn(i + 1, j);
                    }
                }
            }
            for (int i = 0; i < 5; ++ i) {
                if (g[4][i] == '0') {
                    res = 1e9;
                }
            }
            ans = min(ans, res);
            memcpy(g, backup, sizeof backup);
        }
        if (ans <= 6) return ans;
        return -1;
    }
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        int _; cin >> _;
        while (_ -- ) {
            for (int i = 0; i < 5; ++ i) cin >> g[i];
            cout << solve() << endl;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    AcWing 96. 奇怪的汉诺塔

    题意 :

    • 汉诺塔问题,现在有四座塔,n个盘子,请你求出将所有圆盘从塔 A 移动到塔 D,所需的最小移动次数是多少。

    思路 :

    • 首先考虑3塔的汉诺塔问题,设 d [ n ] d[n] d[n]表示求解该n盘3塔问题的最少步数,显然有 d [ n ] = 2 ∗ d [ n − 1 ] + 1 d[n]=2*d[n-1]+1 d[n]=2d[n1]+1,即把前n-1个盘子从A移动到B,然后把第n个盘子从A移动到C,再将n-1个盘子从B移动到C
    • 本题,设f[n]表示求解n盘4塔问题的最少步数,有 f [ n ] = min ⁡ 1 ≤ i ≤ n ( 2 ∗ f [ i ] + d [ n − i ] ) f[n]=\min_{1 \leq i \leq n}(2*f[i]+d[n-i]) f[n]=min1in(2f[i]+d[ni]),其中f[1] = 1,含义为先把i个盘子在4塔模式下从A移到B,然后把n-i个盘子在3塔模式下从A移到D,最后把n个盘子在4塔模式下从B移到D,考虑所有可能的i取最小值
    #include 
    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    const int N = 14;
    
    int d[N], f[N];
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        for (int i = 1; i <= 12; ++ i) d[i] = 2 * d[i - 1] + 1;
        memset(f, 0x3f, sizeof f);
        f[1] = 1;
        for (int i = 1; i <= 12; ++ i) {
            for (int j = 1; j < i; ++ j) {
                f[i] = min(f[i], 2 * f[j] + d[i - j]);
            }
            cout << f[i] << endl;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    分治

    AcWing 97. 约数之和

    题意 :

    • 假设现在有两个自然数 A 和 B,S 是 AB 的所有约数之和。请你求出 Smod9901 的值是多少。
    • 0 ≤ A , B ≤ 5 × 1 0 7 0≤A,B≤5×10^7 0A,B5×107
    • 注意: A 和 B 不会同时为 0。

    思路 :

    • 把A分解质因数,表示为 p 1 c 1 ∗ p 2 c 2 ∗ . . . ∗ p n c n p_1^{c_1}*p_2^{c_2}*...*p_n^{c_n} p1c1p2c2...pncn,那么 A B A^B AB表示为 p 1 B ∗ C 1 ∗ p 2 B ∗ C 2 ∗ . . . ∗ p n B ∗ C n p_1^{B*C_1}*p_2^{B*C_2}*...*p_n^{B*C_n} p1BC1p2BC2...pnBCn A B A^B AB的所有约数表示为集合 ( p 1 k 1 ∗ p 2 k 2 ∗ . . . ∗ p n k n ) (p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n}) (p1k1p2k2...pnkn),其中 0 ≤ k i ≤ B ∗ C i ( 1 ≤ i ≤ n ) 0 \leq k_i \leq B*C_i(1 \leq i \leq n) 0kiBCi(1in)
    • 根据乘法分配律, A B A^B AB的所有约数之和为 ( 1 + p 1 + p 1 2 + . . . + p 1 n ) ∗ ( 1 + p 2 + p 2 2 + . . . + p 2 n ) ∗ . . . (1+p_1+p_1^2+...+p_1^n)*(1+p_2+p_2^2+...+p_2^n)*... (1+p1+p12+...+p1n)(1+p2+p22+...+p2n)...
    • 上式中的每个括号内都是等比数列,如果使用等比数列求和公式,需要做除法,但答案还需要取模,mod运算只对加减乘有分配律,不能直接对分子、分母取模后再做除法;因此,以下,我们使用分治法进行等比数列求和
    • 若c为奇数:
      s u m ( p , c ) = ( 1 + p + . . . + p c − 1 2 ) + ( p c + 1 2 + . . . + p c ) sum(p,c)=(1+p+...+p^{\frac{c-1}{2}})+(p^{\frac{c+1}{2}}+...+p^c) sum(p,c)=(1+p+...+p2c1)+(p2c+1+...+pc)
      s u m ( p , c ) = ( 1 + p + . . . + p c − 1 2 ) + p c + 1 2 ∗ ( 1 + . . . + p c − 1 2 ) sum(p,c)=(1+p+...+p^{\frac{c-1}{2}})+p^{\frac{c+1}{2}}*(1+...+p^{\frac{c-1}{2}}) sum(p,c)=(1+p+...+p2c1)+p2c+1(1+...+p2c1)
      s u m ( p , c ) = ( 1 + p c + 1 2 ) ∗ ( 1 + p + . . . + p c − 1 2 ) sum(p,c)=(1+p^{\frac{c+1}{2}})*(1+p+...+p^{\frac{c-1}{2}}) sum(p,c)=(1+p2c+1)(1+p+...+p2c1)
      s u m ( p , c ) = ( 1 + p c + 1 2 ) ∗ s u m ( p , c − 1 2 ) sum(p,c)=(1+p^{\frac{c+1}{2}})*sum(p,\frac{c-1}{2}) sum(p,c)=(1+p2c+1)sum(p,2c1)
    • 若c为偶数,类似地:
      s u m ( p , c ) = ( 1 + p + . . . + p c − 2 2 ) + p c 2 ∗ ( 1 + . . . + p c − 2 2 ) + p c sum(p,c)=(1+p+...+p^{\frac{c-2}{2}})+p^{\frac{c}{2}}*(1+...+p^{\frac{c-2}{2}})+p^c sum(p,c)=(1+p+...+p2c2)+p2c(1+...+p2c2)+pc
      s u m ( p , c ) = ( 1 + p c 2 ) ∗ s u m ( p , c 2 − 1 ) + p c sum(p,c)=(1+p^{\frac{c}{2}})*sum(p,\frac{c}{2}-1)+p^c sum(p,c)=(1+p2c)sum(p,2c1)+pc
    • 每次可以将问题规模缩小一半,配合快速幂可 l o g ( c ) log(c) log(c)求出等比数列之和
    • 注意mod运算对乘法有分配律,因此快速幂中先对底数mod
    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    const int mod = 9901;
    
    int qmi(int a, int b) {
        a %= mod;
        int res = 1 % mod;
        while (b) {
            if (b & 1) res = res * a % mod;
            a = a * a % mod;
            b >>= 1;
        }
        return res;
    }
    int sum(int p, int c) {
        if (!c) return 1;
        if (c % 2 == 0) return ((1 + qmi(p, c / 2)) % mod * sum(p, c / 2 - 1) % mod + qmi(p, c)) % mod;
        else return (1 + qmi(p, (c + 1) / 2)) % mod * sum(p, (c - 1) / 2) % mod;
    }
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        int A, B;
        cin >> A >> B;
        int res = 1;
        for (int i = 2; i <= A; ++ i) {
            int cnt = 0;
            while (A % i == 0) {
                cnt ++ ;
                A /= i;
            }
            if (cnt) res = (res * sum(i, B * cnt)) % mod;
        }
        if (!A) cout << 0;
        else cout << res;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    (-)分形

    (-)AcWing 98. 分形之城

    0x03 前缀和与差分

    前缀和

    AcWing 99. 激光炸弹

    题意 :

    • 地图上有 N 个目标,用整数 Xi,Yi 表示目标在地图上的位置,每个目标都有一个价值 Wi。注意:不同目标可能在同一位置。现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有目标。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y 轴平行。求一颗炸弹最多能炸掉地图上总价值为多少的目标。

    思路 :

    • 由于x和y分别最大是5000,因此可以用二维前缀和,且我们将边界设为5001
    • 注意r取5001和r中的较小值,否则下标就不对了
    • 注意输入的坐标要+1,前缀和下标从1开始
    • 为了节省空间,即使是二维前缀和也可以将单个值的数组和前缀和数组共用
    • 注意最后算每个边长为S的正方形权值时,下标要从r开始,不能从r+1开始
    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    const int N = 5e3 + 10;
    
    int s[N][N];
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        int n = 5001, m = 5001;
        int cnt, r;
        cin >> cnt >> r;
        
        r = min(5001, r);
        
        while (cnt -- ) {
            int x, y, w;
            cin >> x >> y >> w;
            x ++ ;
            y ++ ;
            s[x][y] += w;
        }
        for (int i = 1; i <= n; ++ i) {
            for (int j = 1; j <= m; ++ j) {
                s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
            }
        }
        int ans = 0;
        for (int i = r; i <= n; ++ i) {
            for (int j = r; j <= m; ++ j) {
                int now = s[i][j] - s[i - r][j] - s[i][j - r] + s[i - r][j - r];
                ans = max(ans, now);
            }
        }
        cout << ans;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    差分

    对于一个给定的数列A,它的差分数列B定义为:
    B [ 1 ] = A [ 1 ] , B [ i ] = A [ i ] − A [ i − 1 ] ( 2 ≤ i ≤ n ) B[1]=A[1],B[i]=A[i]-A[i-1](2 \leq i \leq n) B[1]=A[1],B[i]=A[i]A[i1](2in)

    差分序列B的前缀和序列就是原序列A,前缀和序列S的差分序列也是原序列A

    把序列A的区间[l, r]加上d,其差分序列B的变化为 B l + d , B r + 1 − d B_l+d,B_{r+1}-d Bl+d,Br+1d,其他位置不变

    差分序列与原序列共用一个数组:for (int i = n; i; i -- ) a[i] -= a[i - 1];(原序列下标从1开始)

    AcWing 100. IncDec序列

    题意 :

    • 给定一个长度为 n 的数列 a1,a2,…,an,每次可以选择一个区间 [l,r],使下标在这个区间内的数都加一或者都减一。
    • 求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。
    • 0 < n ≤ 1 0 5 00<n105

    思路 :

    • 求出a的差分序列b,其中 b 1 = a 1 , b i = a i − a i − 1 ( 2 ≤ i ≤ n ) b_1=a_1,b_i=a_i-a_{i-1}(2 \leq i \leq n) b1=a1,bi=aiai1(2in),令 b n + 1 = 0 b_{n+1}=0 bn+1=0
    • 题目对序列a的操作,相当于每次可以选出 b 1 , b 2 , . . . , b n + 1 b_1,b_2,...,b_{n+1} b1,b2,...,bn+1中的任意两个数,一个+1,另一个-1(因为是 b l + 1 , b r + 1 − 1 b_l+1,b_{r+1}-1 bl+1,br+11
    • 目标是把 b 2 , b 3 , . . . , b n b_2,b_3,...,b_{n} b2,b3,...,bn变为全0;最终得到的a序列是由n个 b 1 b_1 b1构成的(因为 b 1 = a 1 , b i = a i − a i − 1 ( 2 ≤ i ≤ n ) b_1=a_1,b_i=a_i-a_{i-1}(2 \leq i \leq n) b1=a1,bi=aiai1(2in)
    • b 1 , b 2 , , . . . , b n + 1 b_1,b_2,,...,b_{n+1} b1,b2,,...,bn+1中任选两个数的方法可分为四类:
      1、选 b i b_i bi b j b_j bj,其中 2 ≤ i , j ≤ n 2 \leq i,j \leq n 2i,jn,这种操作会改变 b 2 , b 3 , . . . , b n b_2,b_3,...,b_{n} b2,b3,...,bn中两个数的值。应该在保证 b i b_i bi b j b_j bj一正一负的情况下,尽量多地采取这种操作
      2、选 b 1 b_1 b1 b j b_j bj,其中 2 ≤ j ≤ n 2 \leq j \leq n 2jn
      3、选 b i b_i bi b n + 1 b_{n+1} bn+1,其中 2 ≤ i ≤ n 2 \leq i\leq n 2in
      4、选 b 1 b_1 b1 b n + 1 b_{n+1} bn+1,这种情况没有意义,相当于浪费了一次操作,一定不是最优解
    • b 2 , b 3 , . . . , b n b_2,b_3,...,b_{n} b2,b3,...,bn中正数总和为p,负数总和的绝对值为q
    • 首先以正负数配对的方式尽量执行第1类操作,可执行 m i n ( p , q ) min(p,q) min(p,q)次,剩余 ∣ p − q ∣ |p-q| pq个未配对,每个可以选与 b 1 b_1 b1 b n + 1 b_{n+1} bn+1配对,即执行2或3操作,共需 ∣ p − q ∣ |p-q| pq
    • 因此,最少操作次数为 m i n ( p , q ) + ∣ p − q ∣ = m a x ( p , q ) min(p,q)+|p-q|=max(p,q) min(p,q)+pq=max(p,q)次,根据 ∣ p − q ∣ |p-q| pq次第2、3类操作的选择情况,能产生 ∣ p − q ∣ + 1 |p-q|+1 pq+1种不同的 b 1 b_1 b1的值,即最终得到的序列a可能有 ∣ p − q ∣ + 1 |p-q|+1 pq+1
    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    const int N = 1e5 + 10;
    
    int n;
    int a[N];
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        cin >> n;
        for (int i = 1; i <= n; ++ i) cin >> a[i];
        for (int i = n; i; -- i) a[i] -= a[i - 1];
        ll p = 0, q = 0;
        for (int i = 2; i <= n; ++ i) {
            if (a[i] > 0) p += a[i];
            else if (a[i] < 0) q -= a[i];
        }
        cout << max(p, q) << endl;
        cout << abs(p - q) + 1;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    AcWing 101. 最高的牛

    题意 :

    • 有 N 头牛站成一行,被编队为 1、2、3…N,每头牛的身高都为整数。当且仅当两头牛中间的牛身高都比它们矮时,两头牛方可看到对方。现在,我们只知道其中最高的牛是第 P 头,它的身高是 H ,剩余牛的身高未知。但是,我们还知道这群牛之中存在着 M 对关系,每对关系都指明了某两头牛 A 和 B 可以相互看见。求每头牛的身高的最大可能值是多少。
    • 1≤N≤5000, 0≤M≤10000

    思路 :

    • M对关系实际上是牛之间身高相对大小关系。具体来说,建立一个数组C,数组起始全为0.若 A i A_i Ai B j B_j Bj可以相互看到(不妨假设 A i < B j A_iAi<Bj)(注意swap!!!!!),则把数组C中下标为 A i + 1 A_{i+1} Ai+1 B j − 1 B_{j-1} Bj1的数都减去1,意思是在 A i A_i Ai B j B_j Bj中间的牛,身高至少要比它们小1
    • 由于第P头牛是最高的,所以最终C[P]一定=0,其他牛与P牛身高差距就体现在数组C中,换言之,最终第i头牛的身高就等于 H + C [ i ] H+C[i] H+C[i]
    • 由于要把数组C中下标为 A i + 1 A_{i+1} Ai+1 B j − 1 B_{j-1} Bj1的数都减去1,朴素为O(NM),哒咩,因此,额外建立一个数组D,对于每对 A i , B j A_i,B_j Ai,Bj,令 D [ A i + 1 ] − 1 , D [ B j ] + 1 D[A_{i+1}]-1,D[B_j]+1 D[Ai+1]1,D[Bj]+1,最后,C数组就是D数组的前缀和,复杂度转换为 O ( N + M ) O(N+M) O(N+M)
    • 注意一条关系可能会输入多次,要记录
    #include 
    #include 
    #define endl '\n'
    #define _(a) cout << #a << ": " << (a) << "    "
    using namespace std;
    typedef long long ll;
    typedef pair<int, int> PII;
    const int N = 5e3 + 10;
    
    int n, p, h, m;
    int d[N];
    set<PII> existed;
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        cin >> n >> p >> h >> m;
        while (m -- ) {
            int a, b;
            cin >> a >> b;
            if (a > b) swap(a, b);
            if (!existed.count({a, b})) {
                existed.insert({a, b});
                d[a + 1] -- ;
                d[b] ++ ;
            }
        }
        for (int i = 1; i <= n; ++ i) {
            d[i] += d[i - 1];
            cout << d[i] + h << endl;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    0x04 二分

  • 相关阅读:
    JVM虚拟机:JVM中垃圾回收器的总结
    Linux设置N天未登录强制冻结
    HJ68 成绩排序
    Elastic Cloud v.s. Zilliz Cloud:性能大比拼
    安全与加密常识(5)自签名证书
    NTP时钟系统为制造业信息化产业提供守时保障
    Weblogic+Oracle11g设置开机自启动
    DNSPod十问党霏霏:充电桩是披着高科技外皮的传统基建?
    Spring学习③__Bean管理
    2023,简历石沉大海?软件测试岗位真的已经饱和了....
  • 原文地址:https://blog.csdn.net/m0_51448653/article/details/126132585