2023/5/14 个人总结---刷题!!!

排序篇

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;
}

本周完结!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

akb000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值