7. 基础算法(II)
7.1 位运算
题目:求
思路:
方法一:快速乘。
类似 4.4 中快速幂的思想。
设
于是有:
又因为
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
int a, b, p;
int mul(int a, int b, int p) {
int ans = 0;
while (b) {
if (b & 1) ans = (ans + a) % p;
a = a * 2 % p;
b >>= 1;
}
return ans;
}
signed main() {
scanf("%lld%lld%lld", &a, &b, &p);
printf("%lld\n", mul(a, b, p));
return 0;
}
方法二:光速乘。
利用 long double
直接计算 long double
计算出 unsigned long long
将其强制转换为
由于 unsigned long long
计算 long long
计算
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ll a, b, p;
ll mul(ll a, ll b, ll p) {
a %= p, b %= p;
ull c = (long double)a * b / p;
ull x = a * b, y = c * p;
ll ans = ((ll)x % p - (ll)y % p + p) % p;
return ans;
}
int main() {
scanf("%lld%lld%lld", &a, &b, &p);
printf("%lld\n", mul(a, b, p));
return 0;
}
7.2 递归与递推
题目:有 -1
。
思路:
显然,已操作过的灯(除非它是关着的)不需要再操作。所以我们可以先用二进制枚举第一行的操作情况,再从第一行枚举到第四行,若位于
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int dx[] = {-1, 0, 1, 0, 0}, dy[] = {0, -1, 0, 1, 0};
int t;
bool a[7][7], tmp[7][7];
void change(int x, int y) {
for (int i = 0; i < 5; ++i) {
int x_ = x+dx[i], y_ = y+dy[i];
if (x_ > 0 && x_ < 6 && y_ > 0 && y_ < 6)
a[x_][y_] ^= 1;
}
}
int main() {
scanf("%d", &t);
while (t -- ) {
for (int i = 1; i <= 5; ++i) {
for (int j = 1; j <= 5; ++j)
scanf("%1d", &a[i][j]);
}
int ans = 7, step = 0;
memcpy(tmp, a, sizeof a);
for (int s = 0; s < 32; ++s) {
step = 0; memcpy(a, tmp, sizeof tmp); // 注意要将原数组复制一遍,防止覆盖
for (int i = 1; i <= 5; ++i) {
if (s >> (i-1) & 1)
step ++, change(1, i);
}
for (int i = 1; i <= 4; ++i) {
for (int j = 1; j <= 5; ++j) {
if (!a[i][j])
step ++, change(i+1, j);
}
}
for (int i = 1; i <= 5; ++i) {
if (!a[5][i]) {
step = 7;
break;
}
}
ans = min(step, ans);
}
printf("%d\n", (ans > 6) ? -1 : ans);
}
return 0;
}
题目:给定两个数
思路:
假设在算数基本定理中,
考虑如何快速计算后面的和式。不妨设
当
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
using namespace std;
typedef long long ll;
const int P = 9901;
int a, b, ans = 1;
unordered_map<int, int> primes;
int pow(int a, int b) {
int ans = 1;
while (b) {
if (b & 1) ans = (ll)ans * a % P;
a = (ll)a * a % P;
b >>= 1;
}
return ans;
}
int sum(int p, int k) {
if (k == 1) return 1;
if (k % 2) return (sum(p, k-1) + pow(p, k-1)) % P;
return sum(p, k/2)*(pow(p, k/2)+1) % P;
}
int main() {
scanf("%d%d", &a, &b);
if (!a) puts("0"), exit(0);
for (int i = 2; i <= a/i; ++i) {
while (a % i == 0) {
a /= i;
primes[i] ++;
}
}
if (a > 1) primes[a] ++;
for (auto p : primes) ans = ans * sum(p.first, p.second*b+1) % P;
printf("%d\n", ans);
return 0;
}
例题:AcWing 98. 分形之城
题目:为了更好地规划城市建设,一座城市的工作人员想出了如下图所示的一种方法:
现在,他们想求出编号为
思路:
定义
在计算
-
若
在左上角:则 顺时针旋转 后为 ,再水平翻转后为 ; -
若
在右上角:则 的 坐标增加了 ,坐标变为 ; -
若
在右下角:则 的 坐标增加了 , 坐标也增加了 ,坐标变为 ; -
若
在左下角:则 逆时针旋转 后为 ,再水平翻转后为 ,又因为 的 坐标增加了 ,所以坐标变为 。
最后用两点之间距离公式计算出
*代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
#define int long long
typedef pair<int, int> pii;
int t, n, a, b;
pii pos(int a, int n) {
if (!n) return make_pair(1, 1);
int t = pow(2, n*2-2);
pii a_ = pos((a-1)%t+1, n-1);
int part = (a-1)/t, x = a_.first, y = a_.second;
if (part == 0) return make_pair(y, x);
else if (part == 1) return make_pair(x, y+(int)pow(2, n-1));
else if (part == 2) return make_pair(x+(int)pow(2, n-1), y+(int)pow(2, n-1));
else return make_pair((int)pow(2, n)-y+1, (int)pow(2, n-1)-x+1);
}
int dist(pii a, pii b) {
return round(sqrt(pow(a.first*10-b.first*10, 2)+pow(a.second*10-b.second*10, 2)));
}
signed main() {
scanf("%lld", &t);
while (t -- ) {
scanf("%lld%lld%lld", &n, &a, &b);
printf("%lld\n", dist(pos(a, n), pos(b, n)));
}
return 0;
}
7.3 前缀和与差分
题目:地图上有
思路:
由于
最后我们枚举正方形的右下角
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5005;
int n, m, ans;
int s[N][N];
int main() {
scanf("%d%d", &n, &m);
m = min(m, 5001);
for (int i = 1; i <= n; ++i) {
int x, y, w; scanf("%d%d%d", &x, &y, &w);
x ++, y ++;
s[x][y] += w;
}
for (int i = 1; i <= 5001; ++i) {
for (int j = 1; j <= 5001; ++j)
s[i][j] += s[i-1][j] + s[i][j-1] - s[i-1][j-1];
}
for (int i = m; i <= 5001; ++i) {
for (int j = m; j <= 5001; ++j)
ans = max(ans, s[i][j]-s[i-m][j]-s[i][j-m]+s[i-m][j-m]);
}
printf("%d\n", ans);
return 0;
}
题目:有一个长度为
思路:
我们求出
:这样可以改变 两个值。我们应在保证 一正一负时,尽量多地采取这种操作。 :这样可以将 减少 。 :这样可以将 增加 。 :这样对 没有影响,相当于浪费了一次操作。
设
*代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int n; ll p, q;
int a[N], b[N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
b[i] = a[i] - a[i-1];
}
for (int i = 2; i <= n; ++i) {
if (b[i] > 0) p += (ll)b[i];
else q += (ll)-b[i];
}
printf("%lld\n%lld\n", max(p, q), abs(p-q)+1);
return 0;
}
7.4 二分
题目:给定一个长度为
思路:
这道题并没有明显的二分的特征。我们可以考虑二分答案,若最优解为
如果我们将子段中的每个数都减去二分的值,就转化为判断“是否存在一个长度不小于
-
求一个子段,它的和最大:我们可以
扫描原数列,初始时 ,每次 增加 ,若 ,则令 。该过程中 的最大值即为所求。 -
求一个子段,它的和最大,子段的长度不小于
:子段和可以转化为前缀和相减的形式。令 ,则所求即为:注意到,随着
的增长, 的取值范围每次只会增加 。所以我们可以用一个变量记录当前最小值,每次与新的取值取最小值即可。
解决了问题 2 之后,我们只需要判断最大字段和是不是非负数,就可以确定二分上下界了。
时间复杂度
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
const double eps = 1e-5;
int n, L;
double a[N], b[N], s[N];
int main() {
scanf("%d%d", &n, &L);
for (int i = 1; i <= n; ++i) scanf("%lf", &a[i]);
double l = -1e6, r = 1e6;
while (r - l > eps) {
double mid = (l + r) / 2;
for (int i = 1; i <= n; ++i) b[i] = a[i] - mid;
for (int i = 1; i <= n; ++i) s[i] = s[i-1] + b[i];
double ans = -1e10, minl = 1e10;
for (int i = L; i <= n; ++i) {
minl = min(minl, s[i-L]);
ans = max(ans, s[i]-minl);
}
if (ans >= 0) l = mid;
else r = mid;
}
printf("%d\n", (int)(r*1000));
return 0;
}
题目:有 compare
函数并传入两个参数 compare
函数返回 true
,否则返回 false
。
思路:假设前
代码:
// Forward declaration of compare API.
// bool compare(int a, int b);
// return bool means whether a is less than b.
class Solution {
public:
vector<int> specialSort(int N) {
vector<int> ans;
ans.push_back(1);
for (int i = 2; i <= N; ++i) {
int l = 0, r = ans.size()-1;
while (l <= r) {
int mid = l + r>> 1;
if (compare(ans[mid], i)) l = mid + 1;
else r = mid - 1;
}
ans.insert(ans.begin()+l, i);
}
return ans;
}
};
7.5 排序
题目:有一个
思路:
显然,交换相邻两列的值对所有行都不会产生影响,交换相邻两行的值对所有列也不会产生影响。所以我们可以将行和列分开计算。这样,原问题就可以转换为环形均分纸牌问题:有

如图
其中,
设
那么所求的最小值即为
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
#define int long long
const int N = 1e5+10;
int n, m, t;
int row[N], col[N];
int c[N];
int check(int n, int a[]) {
if (t % n) return -1;
memset(c, 0, sizeof c);
int avr = t / n, sum = 0;
for (int i = 1; i <= n; ++i) c[i] = sum - (i-1) * avr, sum += a[i];
sort(c+1, c+n+1);
int ans = 0;
for (int i = 1; i <= n; ++i) ans += abs(c[i]-c[(n+1)/2]);
return ans;
}
signed main() {
scanf("%lld%lld%lld", &n, &m, &t);
for (int i = 1; i <= t; ++i) {
int x, y; scanf("%lld%lld", &x, &y);
row[x] ++, col[y] ++;
}
int ansr = check(n, row), ansc = check(m, col);
if (ansr != -1 && ansc != -1) printf("both %lld\n", ansr+ansc);
else if (ansr != -1) printf("row %lld\n", ansr);
else if (ansc != -1) printf("column %lld\n", ansc);
else puts("impossible");
return 0;
}
题目:给定
思路:

如图
- 若
:说明 大于等于当前中位数,要插入到 中; - 若
:说明 小于当前中位数,要插入到 中。
我们还需要维护两个堆的大小关系。若
时间复杂度
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
int p, s, m;
int main() {
scanf("%d", &p);
while (p -- ) {
scanf("%d%d", &s, &m);
printf("%d %d\n", s, (m+1)/2);
int cnt = 0;
priority_queue<int, vector<int>, less<int>> down;
priority_queue<int, vector<int>, greater<int>> up;
for (int i = 1; i <= m; ++i) {
int x; scanf("%d", &x);
if (down.size() && x >= down.top()) up.push(x);
else down.push(x);
if (down.size() > up.size()+1) up.push(down.top()), down.pop();
if (up.size() > down.size()) down.push(up.top()), up.pop();
if (i & 1) {
printf("%d ", down.top());
cnt ++;
if (cnt % 10 == 0) puts("");
}
}
if (cnt % 10) puts("");
}
return 0;
}
题目:有多组测试数据,每组数据包含
思路:显然题目所求的即为
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
const int N = 5e5+10;
int n, a[N], tmp[N];
int merge_sort(int l, int r) {
if (l >= r) return 0;
int mid = l + r >> 1;
int ans = merge_sort(l, mid) + merge_sort(mid+1, r);
int i = l, j = mid+1, k = 1;
while (i <= mid && j <= r) {
if (a[i] <= a[j]) tmp[k ++] = a[i ++];
else tmp[k ++] = a[j ++], ans += mid-i+1;
}
while (i <= mid) tmp[k ++] = a[i ++];
while (j <= r) tmp[k ++] = a[j ++];
for (int i = l, j = 1; i <= r; ++i, ++j) a[i] = tmp[j];
return ans;
}
signed main() {
scanf("%lld", &n);
while (n) {
for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
int ans = merge_sort(1, n);
printf("%lld\n", ans);
scanf("%lld", &n);
}
return 0;
}
7.6 RMQ
题目:给定
思路:
我们可以用 ST 表来完成这道题,其运用的是倍增的思想。令
接下来考虑如何处理询问。令
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 5e5+10, M = 20;
int n, m;
int a[N], f[N][M];
int query(int l, int r) {
int k = log2(r-l+1);
return max(f[l][k], f[r-(1<<k)+1][k]);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), f[i][0] = a[i];
for (int j = 1; (1<<j) <= n; ++j) {
for (int i = 1; i+(1<<j)-1 <= n; ++i)
f[i][j] = max(f[i][j-1], f[i+(1<<j-1)][j-1]);
}
int l, r;
scanf("%d", &m);
while (m --) {
scanf("%d%d", &l, &r);
printf("%d\n", query(l, r));
}
return 0;
}
本文作者:Jasper08
本文链接:https://www.cnblogs.com/Jasper08/p/17461504.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步