排序篇
P1908 逆序对
这一题首先需要了解归并排序是什么
简单叙述原理:类似分治,把一个序列分成两个子序列,使子序列有序,最后合并两个子序列,对子序列也是相同的操作,通过递归实现。
在合并过程中的核心代码:
while (p1 <= M && p2 <= R) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
要求逆序对的数量,只需统计右子序列的每个数与左子序列产生多少个逆序对即可。代码如下:
while (p1 <= M && p2 <= R) {
if (arr[p1] > arr[p2]) {
sum += (M - p1 + 1);
}
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; //
}
完整代码:
#include <iostream>
using namespace std;
long long sum; //逆序对个数
void merge(int* arr,int L,int M,int R) { //归并排序
int *help = new int[R - L + 1]; //开一个辅助数组
int i = 0;
int p1 = L, p2 = M + 1; //分别指向左右序列剩下的第一个数
while (p1 <= M && p2 <= R) {
if (arr[p1] > arr[p2]) { //判断是否产生逆序对
sum += (M - p1 + 1);
}
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
//比较左右序列,小的数先放入help数组
}
//还有剩余的数就直接复制下来
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
for (int j = 0; j <= i - 1; j++) { //将合并好的数复制到原数组
arr[L + j] = help[j];
}
}
void prosort(int* arr,int L,int R) {
if (L == R)return;
int mid = L + ((R - L) / 2);
prosort(arr, L, mid);
prosort(arr, mid + 1, R);
merge(arr, L, mid, R);
}
const int MAX = 5e5 + 5;
int arr[MAX];
int main() {
ios::sync_with_stdio(false);
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
prosort(arr, 0, n - 1);
cout << sum;
return 0;
}
前缀和篇
P3131 [USACO16JAN]Subsequences Summing to Sevens S
刚开始的时候,我想的是先将数组进行前缀和处理,然后暴力枚举,判断两个数相减%7的余数是否为0,找到满足条件的最长区间,代码如下:
for (int i = n; i >= 1; i--) {
for (int j = 0; j <= i - 1; j++) {
if ((f[i] - f[j]) % 7 == 0) {
if (i - j > maxn) {
maxn = i - j;
}
}
}
}
于是便没有悬念的TLE了。。。
之后我得知,两个数相减%7=0,那这两个数%7的余数相同!!!
完整代码:
#include <iostream>
using namespace std;
const int MAX = 5e4 + 5;
typedef struct { //余数相同的起点和终点的坐标
int l, r;
}in;
int f[MAX];
in cd[7];
int main() {
int n;
cin >> n;
int maxn = 0;
for (int i = 1; i <= n; i++) {
cin >> f[i];
f[i] = (f[i] + f[i - 1]) % 7;
if (!cd[f[i]].l && f[i]) //记录相同余数的起点和终点的坐标
cd[f[i]].l = i;
else cd[f[i]].r = i;
}
cd[0].l = 0; //注意:余数为0的起点要为0
for (int i = 0; i < 7; i++) { //求出最长区间
if (cd[i].r - cd[i].l > maxn) {
maxn = cd[i].r - cd[i].l;
}
}
cout << maxn;
return 0;
}
分治篇
P3612 [USACO17JAN]Secret Cow Code S
思路:拿样例来看,现在我们要求第8位,我们已经知道在3串,只需要逆推出在第2串中的位置,2逆推到1也是一个相同的子问题。题目要求复制要先复制最后一个字符,再复制前缀,我们先直接复制前缀:COW− > COWCOW− > COWCOWCOWCOW
现在求3串的8位置,3串长L, 逆推回2串即为8−L / 2位置,但我们复制的时候是先复制最后一个字符, 所以要多减一为8−1−L / 2。
代码如下:
#include <iostream>
using namespace std;
#include<cstring>
typedef long long ll;
string str;
int main() {
ios::sync_with_stdio(false);
ll N;
cin >> str >> N;
while (str.length() < N) {
ll l = str.length();
while (N > l)l *= 2;
l /= 2;
N -= (l + 1);
if (N == 0)N = l;
}
cout << str[N - 1];
return 0;
}
P1226 【模板】快速幂 | 取余运算
板子题,直接放代码:
#include <iostream>
using namespace std;
typedef long long ll;
ll quickPower(ll a, ll b, ll p) { //快速幂
ll sum = 1;
while (b > 0) {
if (b % 2) { //等同(b&1)
sum *= a;
sum %= p;
} //a^b化为(a*a)^(b/2)
a *= a;
a %= p;
b /= 2; //等同(b>>=1)
}
return sum;
}
int main() {
ios::sync_with_stdio(false);
ll a, b, p;
ll s;
cin >> a >> b >> p;
s = quickPower(a, b, p);
cout << a << "^" << b << " mod " << p << "=" << s;
return 0;
}
二分
P1102 A-B 数对
题目要求A-B=C的数对个数,先将原数组排序,枚举每一个数,易知与A对应的B一定是一段连续的区间,我们只需要找到这段区间的左右端点即可。代码如下:
#include <iostream>
using namespace std;
#include <algorithm>
const int MAX = 2e5 + 5;
int arr[MAX];
int main() {
ios::sync_with_stdio(false);
int n, c;
cin >> n >> c;
for (int i = 1; i <= n; i++) {
cin >> arr[i];
}
sort(arr + 1, arr + 1 + n);
int l = 1, r = 1;
long long sum = 0;
for (int i = 1; i <= n; i++) {
while (r <= n && arr[r] - arr[i] <= c)r++;
while (l <= n && arr[l] - arr[i] < c)l++;
sum += r - l;
}
cout << sum << '\n';
return 0;
}
P2249 【深基13.例1】查找
二分查找板子题,需要注意的是在查找相同的数的最小下标时也需要用二分查找。上代码:
#include <iostream>
using namespace std;
//P2249 【深基13.例1】查找
const int MAX = 1e6 + 5;
int n, m;
int a[MAX];
void erfOR(int k) {
int l = 0, r = n - 1;
int mid;
while (l <= r) {
mid = l + (r - l) / 2;
if (a[mid] > k) {
r = mid - 1;
}
else if (a[mid] < k) {
l = mid + 1;
}
else {
if (mid && a[mid - 1] == k) {
int lo = 0, he = mid - 1;
while (lo < he) {
mid = lo + (he - lo) / 2;
if (a[mid] == k)he = mid;
else lo = mid + 1;
}
cout << he + 1 << ' ';
return;
}
cout << mid + 1 << ' ';
return;
}
}
cout << "-1 ";
}
int main() {
ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int k;
while (m--) {
cin >> k;
erfOR(k);
}
return 0;
}
搜索篇
P1825 [USACO11OPEN]Corn Maze S
bfs,类似走迷宫,只不过加了一个传送门,上代码:
#include <iostream>
using namespace std;
#include <queue>
int n, m;
char f[305][305]; //存储输入字符
int dp[305][305]; //标记是否访问过
int dx[4] = { 0,0,1,-1 }; //四个方向
int dy[4] = { 1,-1,0,0 };
typedef struct { //队列结点---坐标和时间
int x, y, t;
}node;
//传送门---因为可以双向使用,所以起点和终点无所谓
node door1[30]; //‘A'-'Z'对应1-26
node door2[30];
void bfs(int x,int y) {
queue<node>q;
q.push(node{ x,y,0 }); //起点入队
int t;
dp[x][y] = 1; //标记已访问
while (!q.empty()) {
node p = q.front();
q.pop();
x = p.x;
y = p.y;
t = p.t;
if (f[x][y] == '=') { //判断终点
cout << t;
return;
}
if (f[x][y] >= 'A' && f[x][y] <= 'Z') { //使用传送门
int c = f[x][y] - 'A' + 1; //判断传送门的位置,查找另一个传送门的坐标
if (door1[c].x == x && door1[c].y == y) {
x = door2[c].x;
y = door2[c].y;
}
else {
x = door1[c].x;
y = door1[c].y;
}
}
for (int i = 0; i < 4; i++) { //继续搜索
int x1 = x + dx[i];
int y1 = y + dy[i];
if (dp[x1][y1] || f[x1][y1] == '#' || x1 < 0 || y1 < 0 || x1>=n || y1>=m) {
continue; //判断是否符合条件
//是否访问过 是否能走 在不在迷宫范围内
}
q.push(node{ x1,y1,t + 1 }); //符合条件的坐标入队,时间+1
dp[x1][y1] = 1; //标记已访问
}
}
}
int main() {
cin >> n >> m;
int x = 0, y = 0;
for (int i = 0; i < n; i++) {
cin >> f[i];
for (int j = 0; j < m; j++) {
if (f[i][j] == '@') { //找到起点
x = i;
y = j;
}
else if (f[i][j] >= 'A' && f[i][j] <= 'Z') { //存储传送门
int c = f[i][j] - 'A' + 1;
if (!door1[c].t) { //因为结点中的t没有使用,所以可以用它来做个判断
//如果该位置的传送门,未被标记,t=0;否则为1
door1[c].x = i;
door1[c].y = j;
door1[c].t = 1;
}
else {
door2[c].x = i;
door2[c].y = j;
door2[c].t = 1; //这里标不标记没关系
}
}
}
}
bfs(x, y); //从起点开始搜索
return 0;
}
P1219 [USACO1.5]八皇后 Checker Challenge
dfs搜索+回溯,上代码:
#include<iostream>
using namespace std;
int a[100], b[100], c[100], d[100];
//a数组表示的是行;
//b数组表示的是列;
//c表示的是左下到右上的对角线;
//d表示的是左上到右下的对角线;
int ans;//解的总数
int n;
void Pt()
{
if (ans <= 2)//保证只输出前三个解,如果解超出三个就不再输出,但后面ans还需要继续叠加
{
for (int k = 1; k <= n; k++)
cout << a[k] << ' ';
cout << '\n';
}
ans++;//ans既是总数,也是前三个排列的判断
}
void dfs(int i)
{
if (i > n)
{
Pt();
return;
}
else
{
for (int j = 1; j <= n; j++)//尝试可能的位置
{
if ((!b[j]) && (!c[i + j]) && (!d[i - j + n]))//如果没有皇后占领,执行以下程序
{
a[i] = j;//标记i排是第j个
b[j] = 1;//宣布占领纵列
c[i + j] = 1;
d[i - j + n] = 1;
宣布占领两条对角线
dfs(i + 1);//进一步搜索,下一个皇后
b[j] = 0;
c[i + j] = 0;
d[i - j + n] = 0;
//回溯
}
}
}
}
int main()
{
cin >> n;
dfs(1);
cout << ans;
return 0;
}
P1596 [USACO10OCT]Lake Counting S
dfs,找连通块。用ans表示水坑的数量,遍历数组,找到一块水地,ans++,并把与这块水地相连的所有水地变成旱地。代码如下:
#include <iostream>
using namespace std;
int n, m;
char f[105][105];
int dx[8] = { 0,0,1,-1,1,-1 ,1,-1 }; //8个方向
int dy[8] = { 1,-1,1,-1,-1,1,0,0 };
void dfs(int x,int y) { //把一个水块周围的所有水块变成旱地
if (x<0 || y<0 || x>=n || y>=m || f[x][y] == '.')return;
f[x][y] = '.';
for (int i = 0; i < 8; i++) {
dfs(x + dx[i], y + dy[i]);
}
}
int main() {
cin >> n >> m;
int ans = 0;
for (int i = 0; i < n; i++) {
cin >> f[i];
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) { //找水块
if (f[i][j]=='W') {
ans++;
dfs(i, j); //从水块所在地开始搜索
}
}
}
cout << ans;
return 0;
}
本周完结!!!