《算法竞赛进阶指南》0x00 基本算法(已更至二分)

包括位运算、递推、递归、二分、排序、倍增、贪心等算法。

一 、位运算

1. a^b

题目描述

求 a 的 b 次方对 p 取模的值。

输入格式

三个整数 a,b,p ,在同一行用空格隔开。

输出格式

输出一个整数,表示a^b mod p的值。

输入样例

3 2 7

输出样例

3 2 7

*数据范围

0≤a,b≤109
1≤p≤109

代码

直接快速幂,防止会爆掉

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr);
const int MAX_N = 1e3 + 50, N = 1e8 + 50, mod = 10000007;

ll a, b, p, ans;
int main() {
    IOS;
    cin >> a >> b >> p;
    ans = 1 % p;//此处模p防止b为0时ans = 1
    while (b) {
        if (b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    
    cout << ans << endl;
    return 0;
}

2. 64位整数乘法

题目描述

求 a 乘 b 对 p 取模的值。

输入格式

三个整数 a,b,p ,在同一行用空格隔开。

输出格式

输出一个整数,表示a * b mod p的值。

输入样例

3 4 5

输出样例

3 10
AND 5
OR 6
XOR 7

数据范围

0≤a,b≤109
1≤p≤109

代码

同上,使用位运算

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr);
const int MAX_N = 1e3 + 50, N = 1e8 + 50, mod = 10000007;

ll a, b, p, ans;
int main() {
    IOS;

    cin >> a >> b >> p;//这里不用担心乘数为0的情况
    while (b) {
        if (b & 1)ans =(ull) (ans + a) % p;
        a = (ull) (a * 2) % p;
        b >>= 1;
    }
    cout << (ull)ans << endl;
    return 0;
}

3.最短Hamilton路径

我现在还在怀疑这个题不是位运算的题

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr);
const int N = 20,M = 1 << N;

int f[M][N],w[N][N];//w表示的是无权图
int main() {
    int n;
    cin >> n;

    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            cin >> w[i][j];

    memset(f, 0x3f, sizeof(f));//因为要求最小值,所以初始化为无穷大
    f[1][0] = 0;//因为零是起点,所以f[1][0]=0;

    for (int i = 0; i < 1 << n; i++)//i表示所有的情况
        for (int j = 0; j < n; j++)//j表示走到哪一个点
            if (i >> j & 1)
                for (int k = 0; k < n; k++)//k表示走到j这个点之前,以k为终点的最短距离
                    if (i >> k & 1)
                        f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);//更新最短距离

    cout << f[(1 << n) - 1][n - 1] << endl;//表示所有点都走过了,且终点是n-1的最短距离
    //位运算的优先级低于'+'-'所以有必要的情况下要打括号
    return 0;
}

4. 起床困难综合症

题目描述

21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳。作为一名青春阳光好少年,atm 一直坚持与起床困难综合症作斗争。通过研究相关文献,他找到了该病的发病原因: 在深邃的太平洋海底中,出现了一条名为 drd 的巨龙,它掌握着睡眠之精髓,能随意延长大家的睡眠时间。
正是由于 drd 的活动,起床困难综合症愈演愈烈, 以惊人的速度在世界上传播。为了彻底消灭这种病,atm 决定前往海底,消灭这条恶龙。历经千辛万苦,atm 终于来到了 drd 所在的地方,准备与其展开艰苦卓绝的战斗。
drd 有着十分特殊的技能,他的防御战线能够使用一定的运算来改变他受到的伤害。具体说来,drd 的防御战线由 n 扇防御门组成。每扇防御门包括一个运算 op 和一个参数 t,其中运算一定是 OR,XOR,AND 中的一种,参数则一定为非负整数。
如果还未通过防御门时攻击力为 x,则其通过这扇防御门后攻击力将变为 x op t。
最终 drd 受到的伤害为对方初始攻击力 x 依次经过所有 n 扇防御门后转变得到的攻击力。
由于 atm 水平有限,他的初始攻击力只能为 0 到 m 之间的一个整数(即他的初始攻击力只能在 0,1,…,m 中任选,但在通过防御门之后的攻击力不受 m 的限制)。
为了节省体力,他希望通过选择合适的初始攻击力使得他的攻击能让 drd 受到最大的伤害,请你帮他计算一下,他的一次攻击最多能使 drd 受到多少伤害。

输入格式

第 1 行包含 2 个整数,依次为 n,m,表示 drd 有 n 扇防御门,atm 的初始攻击力为 0 到 m 之间的整数。
接下来 n 行,依次表示每一扇防御门。每行包括一个字符串 op 和一个非负整数 t,两者由一个空格隔开,且 op 在前,t 在后,op 表示该防御门所对应的操作,t 表示对应的参数。

输出格式

输出一个整数,表示 atm 的一次攻击最多使 drd 受到多少伤害。

数据范围

数据范围

输入样例

3 10
AND 5
OR 6
XOR 7

输出样例

1

样例解释

atm可以选择的初始攻击力为 0,1,…,10。
假设初始攻击力为 4,最终攻击力经过了如下计算
4 AND 5 = 4
4 OR 6 = 6
6 XOR 7 = 1
类似的,我们可以计算出初始攻击力为 1,3,5,7,9 时最终攻击力为 0,初始攻击力为 0,2,4,6,8,10 时最终攻击力为 1,因此 atm 的一次攻击最多使 drd 受到的伤害值为 1。

运算解释

在本题中,选手需要先将数字变换为二进制后再进行计算。如果操作的两个数二进制长度不同,则在前补 0 至相同长度。
OR 为按位或运算,处理两个长度相同的二进制数,两个相应的二进制位中只要有一个为 1,则该位的结果值为 1,否则为 0。
XOR 为按位异或运算,对等长二进制模式或二进制数的每一位执行逻辑异或操作。如果两个相应的二进制位不同(相异),则该位的结果值为 1,否则该位为 0。
AND 为按位与运算,处理两个长度相同的二进制数,两个相应的二进制位都为 1,该位的结果值才为 1,否则为 0。
例如,我们将十进制数 5 与十进制数 3 分别进行 OR、XOR 与 AND 运算,可以得到如下结果:

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr);
const int N = 1e7 + 50,mod = 10000007;
int n, m, ops[N], ts[N];
string op;
int main() {
    IOS;
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        cin >> op;
        if (op == "AND")
            ops[i] = 1;
        else if (op == "OR")
            ops[i] = 2;
        else if (op == "XOR")
            ops[i] = 3;
        int t;
        cin >> t;
        ts[i] = t;
    }

    int bit_n = 29;
    int atm = 0;
    int res = 0;
    for (int bi = 29; bi > -1; bi--) {
        int x = 0;
        for (int i = 0; i < n; i++) {
            if (ops[i] == 1) {
                x &= (ts[i] >> bi);
            } else if (ops[i] == 2) {
                x |= (ts[i] >> bi);
            } else if (ops[i] == 3) {
                x ^= (ts[i] >> bi);
            }
        }

        int y = 1;
        for (int i = 0; i < n; i++) {
            if (ops[i] == 1)
                y &= (ts[i] >> bi);
            else if (ops[i] == 2)
                y |= (ts[i] >> bi);
            else if (ops[i] == 3)
                y ^= (ts[i] >> bi);
        }

        if (atm + (1 << bi) <= m) {
            if (x >= y)
                res |= (x << bi);
            else {
                res |= (y << bi);
                atm |= (1 << bi);
            }
        } else
            res |= (x << bi);
    }

    cout << res << endl;
    return 0;
}

二 、递推与递归

1.递归实现指数型枚举(递归、位运算)

题目描述

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

输入格式

输入一个整数 n。

输出格式

每行输出一种方案。
同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。
对于没有选任何数的方案,输出空行。
本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围

1≤n≤15

输入样例

3

输出样例

3
2
2 3
1
1 3
1 2
1 2 3

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr);
const int N = 1e7 + 50,mod = 10000007;

int n;
vector <int>vc;
void calc (int x){

    if(x == n + 1) {//边界情况
        for (int i = 0; i < vc.size(); i++)
            cout << vc[i] << " ";
        cout << endl;
        return ;
    }
    
    calc(x + 1);//不选x + 1,求解子问题
    
    vc.push_back(x + 1);//选x,将x插入
    calc(x + 1);//求解子问题
    vc.pop_back();//准备回溯到上一个问题之前,还原现场
}
int main() {
    IOS;

    cin >> n;
    calc(1);

    return 0;
}

2.递归实现组合型枚举(递归、剪枝)

问题描述

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

输入格式

两个整数 n,m ,在同一行用空格隔开。

输出格式

按照从小到大的顺序输出所有方案,每行 1 个。
首先,同一行内的数升序排列,相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如 1 3 5 7 排在 1 3 6 8 前面)。

数据范围

n>0 ,0≤m≤n ,n+(n−m)≤25

输入样例

5 3

输出样例

1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 5 
3 4 5 

思考题:如果要求使用非递归方法,该怎么做呢?

和指数型枚举类似,只是将选取n个数改为选取m个数;

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr);
const int N = 1e7 + 50,mod = 10000007;

int n, m;
vector <int>vc;
void calc (int x) {
	//就此处和上述代码不一致,即边界判断条件
    if (vc.size() > m || vc.size() + (n - x + 1) < m)
        return;
    
    if (x == n + 1) {
        for (int i = 0; i < vc.size(); i++)
            cout << vc[i] << " ";
        cout << endl;
        return;
    }
    vc.push_back(x);
    calc(x + 1);
    vc.pop_back();
    calc(x + 1);
}
int main() {
    IOS;

    cin >> n >> m;
    calc(1);

    return 0;
}

3. 递归实现排列型枚举(递归、C++ next_permutation)

题目描述

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

输入格式

一个整数 n。

输出格式

按照从小到大的顺序输出所有方案,每行 1 个。
首先,同一行相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面

数据范围

1≤n≤9

输入样例

3

输出样例

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

代码

法一(c++ next_permutation)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr);
const int N = 1e7 + 50,mod = 10000007;

int n, a[N];
int main() {
    IOS;

    cin >> n;
    for (int i = 0; i < n; i++)
        a[i] = i + 1;

    do {
        cout << a[0];
        for (int i = 1; i < n; i++)
            cout << " " << a[i];
        cout << endl;
    } while (next_permutation(a, a + n));

    return 0;
}

法二(递归)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr);
const int N = 1e7 + 50,mod = 10000007;

int st[N];//存储方案
bool used[N];//标记数字是否被用过,true表示被用过,false表示没被用过
int n;

void dfs(int u) {//当前枚举第u位
    if (u > n) {
        cout << st[1];
        for (int i = 2; i <= n; i++) 
            cout << " " << st[i];//打印方案
        cout << endl;
        return;
    }

    for (int i = 1; i <= n; i++) {//依次枚举每个数
        if (!used[i]) {//当前数可用
            st[u] = i;
            used[i] = true;
            dfs(u + 1);
            //恢复现场
            st[u] = 0;//可省略
            used[i] = false;//不可省
        }
    }
}
int main() {
    IOS;

    cin >> n;
    dfs(1);

    return 0;
}

next_permutation

next_permutation 是 C++ STL 提供的一个函数,用于求一个序列的下一个字典序排列。next_permutation 函数包含在头文件 中。其使用方法如下:

#include <algorithm>
using namespace std;

int main() {
    int a[] = {1, 2, 3};
    do {
        // 处理当前排列
    } while (next_permutation(a, a + 3));
    return 0;
}

其中,next_permutation 接受两个迭代器作为参数,表示要操作的序列的起始位置和结束位置。它会更改这个序列,让它成为下一个字典序排列,并返回一个 bool 值,表示是否成功生成下一个排列。当序列已经是字典序最大的排列时,不再存在下一个排列,函数将返回 false。

next_permutation 的原理其实并不复杂。它实际上是一个标准的全排列算法,对于当前排列从右往左依次找到一个逆序对,该逆序对左边的数字保持不变,右边的数字按照递减排序,然后得到下一个排列。

手动实现 next_permutation 的方法如下:

bool next_permutation(int* a, int n) {
    int i = n - 2;
    while (i >= 0 && a[i] >= a[i + 1]) {
        i--;
    }
    if (i < 0) {
        return false;
    }
    int j = n - 1;
    while (j >= 0 && a[i] >= a[j]) {
        j--;
    }
    swap(a[i], a[j]);
    reverse(a + i + 1, a + n);
    return true;
}

该实现接受一个整数数组 a 和数组的长度 n 作为参数,并修改 a 数组,使其成为下一个字典序排列。如果已经是最大的排列,则返回 false。


4.费解的开关

题目描述

你玩过“拉灯”游戏吗?25 盏灯排成一个 5×5 的方形。每一个灯都有一个开关,游戏者可以改变它的状态。每一步,游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。我们用数字 1 表示一盏开着的灯,用数字 0 表示关着的灯。下面这种状态

10111
01101
10111
10000
11011

在改变了最左上角的灯的状态后将变成:

01111
11101
10111
10000
11011

再改变它正中间的灯后状态将变成:

01111
11001
11001
10100
11011

给定一些游戏的初始状态,编写程序判断游戏者是否可能在 6 步以内使所有的灯都变亮。

输入格式

第一行输入正整数 n,代表数据中共有 n 个待解决的游戏初始状态。以下若干行数据分为 n 组,每组数据有 5 行,每行 5 个字符。每组数据描述了一个游戏的初始状态。各组数据间用一个空行分隔。

输入格式

一共输出 n 行数据,每行有一个小于等于 6 的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。对于某一个游戏初始状态,若 6 步以内无法使所有灯变亮,则输出 −1。

数据范围

0 < n ≤ 500

输入样例

3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

输出样例

3
2
-1

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr)
#define N = 1e8 + 50, M = 1e4 + 50, mod = 1e7 + 9;
const int Min_N = 6;
char g[Min_N][Min_N];
char backup[Min_N][Min_N];
int dx[5] = {-1, 0, 1, 0, 0}, dy[5] = {0, 1, 0, -1, 0};

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) continue;
        g[a][b] ^= 1;
    }
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        for (int i = 0; i < 5; i++)
            cin >> g[i];
        int res = 7;
        for (int op = 0; op < 32; op++) {
            memcpy(backup, g, sizeof g);
            int step = 0;
            for (int i = 0; i < 5; i++) {
                if (op >> i & 1) {
                    step++;
                    turn(0, 4 - i);
                }
            }
            for (int i = 0; i < 4; i++) {
                for (int j = 0; j < 5; j++) {
                    if (g[i][j] == '0') {
                        step++;
                        turn(i + 1, j);
                    }
                }
            }
            bool dark = false;
            for (int i = 0; i < 5; i++) {
                if (g[4][i] == '0') {
                    dark = true;
                    break;
                }
            }
            if (!dark) res = min(res, step);
            memcpy(g, backup, sizeof backup);
        }
        if (res > 6) res = -1;
        cout << res << endl;
    }
    return 0;
}

5.奇怪的汉诺塔

题目描述

汉诺塔问题,条件如下:

1、这里有 A、B、C和 D 四座塔。

2、这里有 n 个圆盘,n 的数量是恒定的。

3、每个圆盘的尺寸都不相同。

4、所有的圆盘在开始时都堆叠在塔 A 上,且圆盘尺寸从塔顶到塔底逐渐增大。

5、我们需要将所有的圆盘都从塔 A 转移到塔 D 上。

6、每次可以移动一个圆盘,当塔为空塔或者塔顶圆盘尺寸大于被移动圆盘时,可将圆盘移至这座塔上。

请你求出将所有圆盘从塔 A 移动到塔 D,所需的最小移动次数是多少。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kDHsiDhD-1688784706431)(https://www.acwing.com/media/article/image/2019/01/10/19_acbb764014-%E6%B2%B3%E5%86%85%E5%A1%94.jpg)]
汉诺塔塔参考模型

输入格式

没有输入

输出格式

对于每一个整数 n,输出一个满足条件的最小移动次数,每个结果占一行。

数据范围

1 ≤ n ≤ 12

代码

#include<bits/stdc++.h>

using namespace std;
const int N = 15;
const int M = 12;
int f[N], d[N];

int main() {
    for (int i = 1; i <= M; i++)
        d[i] = 2 * d[i - 1] + 1;
    memset(f, 0x3f, sizeof(f));
    f[1] = 1;
    for (int i = 2; i <= M; i++)
        for (int j = 1; j < i; j++)
            f[i] = min(f[i], 2 * f[j] + d[i - j]);
    for (int i = 1; i <= M; i++)
        cout << f[i] << endl;
    return 0;
}

6. 约数之和

题目描述

假设现在有两个自然数 A和 B,S 是 AB的所有约数之和

请你求出 S mod 9901 的值是多少。

输入格式

在一行中输入用空格隔开的两个整数 A 和 B。

输出格式

输出一个整数,代表 S mod 9901的值。

数据范围

0 ≤ A,B ≤ 5×107

输入样例

2 3

输出样例

15

思路

这里实现一个sum函数,sum(p, k)表示p0+p1+…+pk−1

思路,当k为偶数时,sum(p, k)可以拆解成p0+p1+…+pk/2−1+pk/2+pk/2+1+…+pk−1

即 p0+p1+…+pk/2−1+pk/2∗(p0+p1+…+pk/2−1), 也就是 sum(p, k/2)+pk/2∗sum(p,k/2)

进一步化简(pk/2+1)∗sum(p,k/2)当k为奇数时,为了更方便调用我们写的偶数项情况,可以单独拿出最后一项,把剩下的项转化为求偶数项的情况来考虑,

再加上最后一项,就是奇数项的情况了。也即sum(p,k−1)+pk−1

ll sum(ll p, ll k) {//求和
    if (k == 1)
        return 1;
    if (k % 2 == 0) {
        return (ll) (pow(p, k / 2) + 1) * sum(p, k / 2) % mod;
    }
    return ((pow(p, k - 1) + sum(p, k - 1)) % mod);
}

代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1e7 + 50, M = 1e4 + 50, mod = 9901, MAX_N = 1e3 + 50;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr)

unordered_map<int, int> primes;//存储因数及因数个数
int A, B;

void div(int n) {//分解A的因数
    for (int i = 2; i < n / i; i++) {
        if (n % i == 0) {
            while (n % i == 0) {
                primes[i]++;
                n /= i;
            }
        }
    }
    if (n != 1)
        primes[n]++;
}

ll pow(ll a, ll b) {//快速幂  a ^ b
    ll res = 1 % mod;
    while (b) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

ll sum(ll p, ll k) {//求和
    if (k == 1)
        return 1;
    if (k % 2 == 0) {
        return (ll) (pow(p, k / 2) + 1) * sum(p, k / 2) % mod;
    }
    return ((pow(p, k - 1) + sum(p, k - 1)) % mod);
}

int main() {
    IOS;

    cin >> A >> B;
    div(A);
    ll s = 1;
    for (auto it: primes) {
        int p = it.first, k = it.second * B;
        s = (ll) s * sum(p, k + 1) % mod;
    }
    if (!A) s = 0;
    cout << s << endl;
    return 0;
}

7.分形之城

题目描述

城市的规划在城市建设中是个大问题。

不幸的是,很多城市在开始建设的时候并没有很好的规划,城市规模扩大之后规划不合理的问题就开始显现。

而这座名为 Fractal 的城市设想了这样的一个规划方案,如下图所示:

city.png

当城区规模扩大之后,Fractal 的解决方案是把和原来城区结构一样的区域按照图中的方式建设在城市周围,提升城市的等级。

对于任意等级的城市,我们把正方形街区从左上角开始按照道路标号。

虽然这个方案很烂,Fractal 规划部门的人员还是想知道,如果城市发展到了等级 N,编号为 A 和 B 的两个街区的直线距离是多少。

街区的距离指的是街区的中心点之间的距离,每个街区都是边长为 1010 米的正方形。

输入格式

第一行输入正整数 n,表示测试数据的数目。

以下 n 行,输入 n 组测试数据,每组一行。

每组数据包括三个整数 N,A,B表示城市等级以及两个街区的编号,整数之间用空格隔开。

输出格式

一共输出 n 行数据,每行对应一组测试数据的输出结果,结果四舍五入到整数

思路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iIyJM3Xn-1688784706432)(https://cdn.acwing.com/media/article/image/2020/07/16/24278_a25d1188c7-%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20200716174849.png)]

代码

#include <iostream>
#include <cmath>

#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PLL;

PLL calc(LL n, LL m) {
    if (n == 0) return {0, 0};
    LL len = 1ll << (n - 1); // n - 1级图构成n级图平移时的单位长度
    LL cnt = 1ll << (n - 1) * 2; // n - 1级图中所含的元素个数
    LL cnk = m / cnt; // 在n级图中,编号为m的元素所属块的编号
    LL idx = m % cnt; // 在n级图中,编号为m的元素在所属块中的编号
    PLL pos = calc(n - 1, idx); // 在n级图中,编号为m的元素在所属块中的坐标
    LL x = pos.x, y = pos.y;
    // 根据n级图中某个块的编号和这个块中某个元素的坐标,确定n - 1级图的坐标变换
    // 注意:离散坐标系跟实数坐标系有些许差别,不考虑这些差别
    // 结果必然是抓耳挠腮。hh
    if (cnk == 0) return {y, x}; // 没有平移,虽是旋转,坐标关系容易确定
    if (cnk == 1) return {x, y + len}; // 单纯平移
    if (cnk == 2) return {x + len, y + len}; // 单纯平移
    if (cnk == 3) return {len * 2 - y - 1, len - x - 1}; // 旋转又平移
}

LL rounding(double a) {
    LL b;
    if (a > 0) b = (a * 2 + 1) / 2;
    else b = (a * 2 - 1) / 2;
    return b;
}

int main() {
    int times;
    cin >> times;
    while (times--) {
        LL n, a, b;
        cin >> n >> a >> b;
        PLL pa = calc(n, a - 1);
        PLL pb = calc(n, b - 1);
        LL x = pa.x - pb.x, y = pa.y - pb.y;
        double dist = sqrt(x * x + y * y);
        cout << rounding(dist) << endl;
    }
    return 0;
}

三 、前缀和与差分

1. 激光炸弹

题目描述

地图上有 N 个目标,用整数 Xi,Y!i 表示目标在地图上的位置,每个目标都有一个价值 Wi

注意:不同目标可能在同一位置。

现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有目标。

激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y 轴平行。

求一颗炸弹最多能炸掉地图上总价值为多少的目标。

输入格式

第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形包含的横纵位置数量,数据用空格隔开。

接下来 N 行,每行输入一组数据,每组数据包括三个整数 Xi,Yi,Wi分别代表目标的 x 坐标,y 坐标和价值,数据用空格隔开。

输出格式

输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。

数据范围

0≤R≤109
0<N≤10000,
0≤Xi,Yi≤5000,
0≤Wi≤1000

输入样例

2 1
0 0 1
1 1 1

输出样例

1

代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1e7 + 50, M = 5050, mod = 9901, MAX_N = 1e3 + 50;
const double PI = 3.1415926;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr)

int n, m, cnt, R;
int s[M][M];

int main() {
    cin >> cnt >> R;
    R = min(5001, R);

    n = m = 5001;
    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 res = 0;

    // 枚举所有边长是R的矩形,枚举(i, j)为右下角
    for (int i = R; i <= n; i++)
        for (int j = R; j <= m; j++)
            res = max(res, s[i][j] - s[i - R][j] - s[i][j - R] + s[i - R][j - R]);

    cout << res << endl;

    return 0;
}

2. IncDec序列

题目描述

给定一个长度为 n 的数列 a1,a2,…,a!n每次可以选择一个区间 [l,r][�,�],使下标在这个区间内的数都加一或者都减一。

求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。

输入格式

第一行输入正整数 n。

接下来 n 行,每行输入一个整数,第 i+1 行的整数代表 ai

输出格式

第一行输出最少操作次数。

第二行输出最终能得到多少种结果。

数据范围

0<n≤105,
0≤ai<2147483648(231)

输入样例

4
1
1
2
2

输出样例

1
2

代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1e7 + 50, M = 5050, mod = 9901, MAX_N = 1e3 + 50, INF = 0x3f3f3f3f;
const double PI = 3.1415926;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr)

int b[N], a[N], n;
ll pos, neg;

int main() {
    IOS;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        b[i] = a[i] - a[i - 1];
    }
    for (int i = 2; i <= n; i++) {
        if (b[i] > 0) pos += b[i];
        else neg -= b[i];
    }
    cout << min(pos, neg) + abs(pos - neg) << endl;
    cout << abs(pos - neg) + 1;
    return 0;
}

3. 最高的牛

题目描述

有 N 头牛站成一行,被编队为 1、2、3…N,每头牛的身高都为整数。

当且仅当两头牛中间的牛身高都比它们矮时,两头牛方可看到对方。

现在,我们只知道其中最高的牛是第 P 头,它的身高是 H ,剩余牛的身高未知。

但是,我们还知道这群牛之中存在着 M 对关系,每对关系都指明了某两头牛 A 和 B 可以相互看见。

求每头牛的身高的最大可能值是多少。

输入格式

第一行输入整数 N ,P ,H ,M 数据用空格隔开。

接下来 M 行,每行输出两个整数 A 和 B ,代表牛 A 和牛 B 可以相互看见,数据用空格隔开。

输出格式

一共输出 N 行数据,每行输出一个整数。

第 i 行输出的整数代表第 i 头牛可能的最大身高。

数据范围

1≤N≤5000,
1≤H≤1000000,
1≤A,B≤10000,
0≤M≤10000

输入样例

9 3 5 5
1 3
5 3
4 3
3 7
9 8

输出样例

5
4
5
3
4
4
5
5
5

注意

  • 此题中给出的关系对可能存在重复

思路

差分+区间处理

我把这题的样例写下来,模拟一遍,找到了规律,发现其实很简单,方法就是把所有值初始化为H,然后再把A到之间的数减1就可以了。
分析:

题目中说对于两头牛它们可以互相看见,说明两牛之间的牛的身高都比这两只低,因此根据最优的原则,我们可知中间的牛可以都比这两只小1即可 。

现在我们考虑关系会不会有交叉的情况。
假设i<j<k<l;存在关系ik和jl,因为存在关系ik,因此k的身高大于j,又因为存在jl,所以j的身高大于k,前后互相矛盾,因此不存在关系存在交叉的情况。

所以对于该问题,我们可以假设全部都是最高身高,然后每出现一对关系,就将他们之间的牛的身高全减1,因为涉及区间加减1,我们可以采用差分和前缀和的关系来解决该问题,具体实现看代码,注意关系判重。

代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1e7 + 50, M = 5050, mod = 9901, MAX_N = 1e3 + 50, INF = 0x3f3f3f3f;
const double PI = 3.1415926;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr)
long long n, h, p, a[10100], b[10100], m, x[5100];
bool r;

int main() {
    cin >> n >> p >> h >> m;
    for (long long i = 1; i <= m; i++) {
        cin >> a[i] >> b[i];
        if (a[i] > b[i])
            swap(a[i], b[i]);
        r = true;
        for (long long j = 1; j < i; j++) {
            if (a[i] == a[j] && b[i] == b[j]) {
                r = false;
                break;
            }
        }
        if (r) {
            x[a[i] + 1]--;
            x[b[i]]++;
        }
    }                           
    x[0] = h;
    for (long long i = 1; i <= n; i++) {
        x[i] += x[i - 1];
        cout << x[i] << endl;
    }
    return 0;
}

四 、二分

1. 最佳牛围栏

题目描述

农夫约翰的农场由 N 块田地组成,每块地里都有一定数量的牛,其数量不会少于 11 头,也不会超过 2000 头。

约翰希望用围栏将一部分连续的田地围起来,并使得围起来的区域内每块地包含的牛的数量的平均值达到最大。

围起区域内至少需要包含 F 块地,其中 F 会在输入中给出。

在给定条件下,计算围起区域内每块地包含的牛的数量的平均值可能的最大值是多少。

输入格式

第一行输入整数 N 和 F,数据间用空格隔开。

接下来 N 行,每行输入一个整数,第 i+1 行输入的整数代表第 i 片区域内包含的牛的数目。

输出格式

输出一个整数,表示平均值的最大值乘以 1000 再 向下取整(double强转int) 之后得到的结果。

数据范围

1≤N≤100000,
1≤F≤N

输入样例

10 6
6 
4
2
10
3
8
5
9
4
1

输出样例

6500

代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1e7 + 50, M = 5050, mod = 9901, MAX_N = 1e3 + 50, INF = 0x3f3f3f3f;
const double PI = 3.1415926;
#define IOS ios_base::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr)

int n, m, cows[N];
double sum[N], l, r, mv;

bool check (double avg) {
    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i - 1] + cows[i] - avg;
    }

    mv = 0;
    for (int i = 0, j = m; j <= n; j++, i++) {
        mv = std::min (mv, sum[i]);
        if (sum[j] - mv >= 0)
            return true;
    }
    return false;
}

int main () {
    IOS;

    std::cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        std::cin >> cows[i];
        r = std::max (r, (double) cows[i]);
    }

    while (r - l > 1e-5) {
        double mid = (l + r) / 2;
        if (check (mid)) l = mid;
        else r = mid;
    }
    std::cout << (int) (r * 1000) << endl;

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值