算法练习qwq

不再更新(因为要重构一下了)

分治思想

01 平面最近点对

题目描述

给定平面上 n n n 个点,找出其中的一对点的距离,使得在这 n n n 个点的所有点对中,该距离为所有点对中最小的

输入

第一行: n n n ,保证 2 ≤ n ≤ 200000 2\le n\le 200000 2n200000
接下来 n n n 行:每行两个实数: x y x y xy,表示一个点的行坐标和列坐标,中间用一个空格隔开。
输出
仅一行,一个实数,表示最短距离,精确到小数点后面 4 4 4 位。

样例
输入

3
1 1
1 2
2 2

输出

1.0000

代码

#include
using namespace std;
const double INF = 1e20;
struct Point{
    double x;
    double y;
};
Point p[200010];
int ans[200010];
double dis(Point p1, Point p2){
    return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}
bool cmpy(int a, int b){
	return p[a].y < p[b].y;
} 
bool cmpxy(Point p1, Point p2){
	if(p1.x != p2.x){
		return p1.x < p2.x;
	}
	return p1.y < p2.y;
}
double divide(int left, int right){
	// 只有一个点 
	if(left == right) 
		return INF;
	// 有两个点 
	if(left + 1 == right) 
		return dis(p[left],p[right]);
	// 多个点,再分 
	int m = (left + right) / 2;
	double d = min(divide(left,m),divide(m+1,right));
	int i, j, k = 0;
	// 通过横坐标确定[x-d,x+d]区域
	for(int i = left; i <= right; i++){
		if(abs(p[m].x - p[i].x) <= d){
			ans[k++] = i;
		}
	} 
	// 将这些点按照纵坐标升序
	sort(ans, ans+k,cmpy);
	// 线性扫描
	for(i = 0; i < k - 1; i++){
		for(j = i + 1; j < k; j++ ){
			if((p[ans[j]].y - p[ans[i]].y) < d){
				double distance = dis(p[ans[i]],p[ans[j]]);
				if(distance < d){
					d = distance;
				}
			} 
			
		}
	} 
	return d;
} 
int main(void){
	int n;
	cin >> n;
	for(int i = 0; i < n; i++){
		cin >> p[i].x >> p[i].y;
	}
	sort(p,p+n,cmpxy);
	cout << fixed << setprecision(4) << divide(0,n-1);
	return 0;
	
}

02 逆序对

模拟归并 + 数组合并

// 前置思想:归并排序
#include
using namespace std;
int a[500000] = {0};
void merge(int a[], int left, int right, int mid){
	int temp[right - left + 1] = {0};
	int p1 = left, p2 = mid + 1, p = 0;
	// 比较两个有序小数组的元素,依次放入大数组中
	while(p1 <= mid && p2 <= right){
		if(a[p1] <= a[p2]){
			temp[p++] = a[p1++];
		}else{
			temp[p++] = a[p2++];
		}
	}
	// 右侧小数组已排序完毕,左侧小数组还有剩余,将左侧小数组元素依次放入大数组尾部
	while(p1 <= mid){
		temp[p++] = a[p1++];
	}
	// 左侧小数组已排序完毕,右侧小数组还有剩余,将右侧小数组元素依次放入大数组尾部
	while(p2 <= right){
		temp[p++] = a[p2++];
	}
	// 回到最初的数组
	for(int i = 0; i < right - left + 1; i++){
		a[left + i] = temp[i];
	} 
}
void divide(int a[], int left, int right){
	if(left >= right){
		return ;
	}
	int mid = (left + right) / 2;
	divide(a, left, mid);
	divide(a, mid+1, right);
	merge(a, left, right, mid);
}
int main(void){
	int n;
	cin >> n;
	for(int i = 0; i < n; i++){
		cin >> a[i];
	}
	divide(a, 0, n-1);
	for(int i = 0; i < n; i++){
		cout << a[i] << " ";
	}
	return 0;
	
}

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj i < j ii<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

输入

第一行,一个数 n n n,表示序列中有 n n n个数。
第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109
输出
输出序列中逆序对的数目。

样例
输入

6
5 4 2 6 3 1

输出

11

提示

对于 40 % 40\% 40% 的数据, n ≤ 2500 n \leq 2500 n2500
对于 60 % 60\% 60% 的数据, n ≤ 4 × 1 0 4 n \leq 4 \times 10^4 n4×104
对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105

代码

#include
using namespace std;
typedef long long LL;
LL a[500000] = {0};
LL count1 = 0;// c++的库函数有关键字count,所以会冲突了,模糊不清。 
void merge(LL a[], LL left, LL right, LL mid){
	LL temp[right - left + 1];
	LL p1 = left, p2 = mid + 1, p = 0;
	// 比较两个有序小数组的元素,依次放入temp数组中
	while(p1 <= mid && p2 <= right){
		if(a[p1] <= a[p2]){
			temp[p++] = a[p1++];
		}else{
			count1 += mid - p1 + 1;
			// 这里不是单纯的++
			/* 当我们将左区间与右区间的数字依次比较时,
				如果左区间下标为i的数字大于右区间下标为j的数字,
				那么我们可以得到对于左区间序列从i到序列最左边的数字都比右区间标为j的数大
			*/ 
			temp[p++] = a[p2++];
		}
	}
	// 右侧小数组已排序完毕,左侧小数组还有剩余,将左侧小数组元素依次放入temp数组尾部
	while(p1 <= mid){
		temp[p++] = a[p1++];
	}
	// 左侧小数组已排序完毕,右侧小数组还有剩余,将右侧小数组元素依次放入temp数组尾部
	while(p2 <= right){
		temp[p++] = a[p2++];
	}
	// 回到最初的数组
	for(int i = 0; i < right - left + 1; i++){
		a[left + i] = temp[i];
	} 
}
void divide(LL a[], LL left, LL right){
	if(left >= right){
		return ;
	}
	LL mid = (left + right) / 2;
	divide(a, left, mid);
	divide(a, mid+1, right);
	merge(a, left, right, mid);
}
int main(void){
	LL n;
	cin >> n;
	for(LL i = 0; i < n; i++){
		cin >> a[i];
	}
	divide(a, 0, n-1);
	cout << count1;
	return 0;
	
}

03 半数集

题目描述

给定一个自然数 n n n,有 n n n开始可以依次产生半数集 s e t ( n ) set(n) set(n)中的数如下:
(1) n ∈ s e t ( n ) n∈set(n) nset(n)
(2) 在 n n n的左边加上一个自然数,但该自然数不能超过最近添加的数的一半;
(3) 按此规则进行处理,直到不能再添加自然数为止。
例如, s e t ( 6 ) = { 6 , 16 , 26 , 126 , 36 , 136 } set(6)=\{6,16,26,126,36,136\} set(6)={6,16,26,126,36,136}。半数集 s e t ( 6 ) set(6) set(6)中有 6 6 6个元素。
注意半数集是多重集(存在重复元素)。

输入

自然数 n n n 1 ≤ n ≤ 10000 1 \le n \le 10000 1n10000

输出

s e t ( n ) set(n) set(n)的元素个数。

样例
输入

23

输出

74

代码
比如6前添了3,那么前面会自动再形成3的半数集,故通过分治递归可解。重复数多,用记忆化记录已经计算过的,减少重复计算耗时。

#include
using namespace std;
typedef long long ll;
ll a[10010] = {0};
ll calc(ll x);
int main(void){
	ll n;
	cin >> n;
	cout << calc(n);
	return 0;
}
ll calc(ll x){
	if(a[x] != 0) return a[x];
	ll ret = 1;
	if(x > 1){
		for(ll i = 1; i <= x / 2; i++){
			ret += calc(i);
		}
	}
	a[x] = ret;
	return ret;
}

04 Moo

题目描述

奶牛 B e s s i e Bessie Bessie 最近在学习字符串操作,它用如下的规则逐一的构造出新的字符串:
S ( 0 ) = S ( 0 ) = m o o S(0) =S(0)= moo S(0)=S(0)=moo
S ( 1 ) = S ( 0 ) + m + o o o + S ( 0 ) = m o o + m + o o o + m o o = m o o m o o o m o o S(1) =S(0)+ m + ooo + S(0) =moo + m + ooo + moo = moomooomoo S(1)=S(0)+m+ooo+S(0)=moo+m+ooo+moo=moomooomoo
S ( 2 ) = S ( 1 ) + m + o o o o + S ( 1 ) = m o o m o o o m o o + m + o o o o + m o o m o o o m o o = = m o o m o o o m o o m o o o o m o o m o o o m o o S(2) = S(1)+ m + oooo + S(1) = moomooomoo + m + oooo +moomooomoo == moomooomoomoooomoomooomoo S(2)=S(1)+m+oooo+S(1)=moomooomoo+m+oooo+moomooomoo==moomooomoomoooomoomooomoo
B e s s i e Bessie Bessie 就这样产生字符串,直到最后产生的那个字符串长度不小于读入的整数 N N N 才停止。
通过上面观察,可以发现第 k k k 个字符串是由:第 k − 1 k-1 k1 个字符串 + m + ( k + 2 + m + (k+2 +m+(k+2 o ) + o) + o)+ k − 1 k-1 k1个字符串连接起来的。
现在的问题是:给出一个整数 N ( 1 ≤ N ≤ 1 0 9 ) N (1 \leq N \leq 10^9) N(1N109),问第 N N N 个字符是字母 m m m 还是 o o o

输入

一个正整数 N N N

输出

一个字符, m m m 或者 o o o

样例
输入

11

输出

m

代码

#include
using namespace std;
typedef long long ll;
vector<ll> len;
char calc(ll n, ll k);
int main(void){
	ll n;
	cin >> n;
	len.push_back(0);
	// 要以0起头,因为后面算法的原因,要涵盖初始的moo 
	cout << calc(n,0);
	return 0;
}
char calc(ll n, ll k){
	if(n <= len[k] + 1 + k + 2 + len[k]){
		if(n <= len[k]){
			calc(n, k - 1);
		}else if(n >= len[k] + 1 + k + 2 + 1){
			calc(n - (len[k] + 1 + k + 2), k - 1);
		}else{
			if(n == len[k] + 1){
				return 'm';
			}else{
				return 'o';
			}
		}
		
	}else{
		len.push_back(len[k] + 1 + k + 2 + len[k]);
		return calc(n, k + 1); 
	}
}

05 处理瓶盖(最小值最大)

二分查找(最大值最小和最小值最大)

模板1 找最大值中的最小

int main(){
	int l;
	int r;
	while(l < r){
		int mid = (l + r)/ 2;
		if(check()){
			// mid满足条件,找最小的,故区间范围为[l,mid]
			r = mid;  
		}else{
			l = mid + 1;   
		}
		//最后的l,r是答案 因为 l == r ,最终就是答案。
	} 		
}

模板2 找最小值中的最大

int main()
{
	int l;
	int r;
	while(l < r){
		// 这里要 l + r +1 要不然会死循环
		int mid = (l + r + 1)/ 2;  
		if(check()){
			// mid满足条件,找最大的,故区间范围为[mid,r]
			l = mid;         
		}else{
			r = mid - 1;     
		}
	} 
	//最后的l,r是答案 因为 l == r
}

题目描述

小南是个贪玩的孩子,他在地上丢了 A A A个瓶盖,为了简化问题,我们可以当作这 A A A个瓶盖丢在一条直线上,现在他想从这些瓶盖里找出 B B B个,使得距离最近的 2 2 2个距离最大,他想知道,最大可以到多少呢?

输入

第一行,两个整数, A , B A,B A,B ( B ≤ A ≤ 100000 ) (B \le A \le 100000) (BA100000)
第二行, A A A个整数,分别为这 A A A个瓶盖坐标。

输出

仅一个整数,为所求答案。

样例
输入

5 3
1 2 3 4 5​

输出

2

代码

#include
using namespace std;
int spot[100010] = {0};
int a, b;
bool check(int x);
int main(void){
	cin >> a >> b;
	for(int i = 0; i < a; i++){
		cin >> spot[i];
	}
	sort(spot,spot+a);
	int r = spot[a-1] - spot[0];
	int l = 1;
	while(l < r){
		int mid = (l + r + 1) / 2;
		if(check(mid)){
			l = mid;
		}else{
			r = mid - 1;
		}
	}
	cout << l;
	return 0;
} 
bool check(int x){
	int prev = 0, total = 1;
	for(int i = 1; i < a; i++){
		if(spot[i] - spot[prev] >= x){
			total++;
			prev = i;
		}
	}
	if(total >= b){
		return true;
	}else{
		return false;
	}
}

前缀和与差分

前缀和与差分
二维差分
算法练习qwq_第1张图片
算法练习qwq_第2张图片

01 地毯

问题描述

n × n n\times n n×n 的格子上有 m m m 个地毯。
给出这些地毯的信息,问每个点被多少个地毯覆盖。

输入

第一行,两个正整数 n , m n,m n,m。意义如题所述。
接下来 m m m 行,每行两个坐标 ( x 1 , y 1 ) 和 ( x 2 , y 2 ) (x_1,y_1) 和 (x_2,y_2) (x1,y1)(x2,y2),代表一块地毯,左上角是 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),右下角是 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)
坐标起始为 1 1 1,而不是 0 0 0

输出

输出 n n n 行,每行 n n n 个正整数。
i i i 行第 j j j 列的正整数表示 ( i , j ) (i,j) (i,j)这个格子被多少个地毯覆盖。

样例
输入

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

输出

0 1 1 1 0
0 1 1 0 0
0 1 2 1 1
0 0 1 1 1
0 0 1 1 1

提示

对于 20 % 20\% 20% 的数据, n ≤ 50 , m ≤ 100 n\le 50,m\le 100 n50m100
对于 100 % 100\% 100% 的数据有,有 n ≤ 1000 , m ≤ 10000 n\le 1000,m\le 10000 n1000m10000

代码

#include
using namespace std;
int p[1010][1010] = {0};
int main(void){
	int n, m;
	cin >> n >> m;
	int x1, y1, x2, y2;
	for(int i = 0; i < m; i++){
		cin >> x1 >> y1 >> x2 >> y2;
		p[x1][y1]++;
		p[x2+1][y1]--;
		p[x1][y2+1]--;
		p[x2+1][y2+1]++;
	}
	for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            p[i][j] = p[i][j] + p[i-1][j] + p[i][j-1] - p[i-1][j-1];
            cout << p[i][j] << " ";
        }
        cout<<endl;
    }
    return 0;
}

搜索算法(DFS BFS)

01 水洼(BFS)

问题描述

长沙最近连日降雨,导致中南大学的一处 N × M N×M N×M的园子积了水。现在想请你算一下,这个园子里总共有多少个水洼

输入

首先输入 n n n m m m代表园子的大小
接着输入园子的构造 . . .代表图, w w w代表水洼

输出

输出水洼的数量

样例
输入

10 12
W…WW.
.WWW…WWW
…WW…WW.
…WW.
…W…
…W…W…
.W.W…WW.
W.W.W…W.
.W.W…W.
…W…W.

输出

3

代码
基本思想就是找到一块水洼后,就扩散寻找他8邻域范围(递归进行)

#include
using namespace std;
int n, m;
int a[1000][1000] = {0};
void find_water(int x, int y);
int main(void){
	cin >> n >> m;
	char c;
	for(int i = 0; i < n; i++){
		for(int j = 0; j < m; j++){
			cin >> c;
			if(c == 'W'){
				a[i][j] = 1;
			}
		}
	};
	int count = 0;
	for(int i = 0; i < n; i++){
		for(int j = 0; j < m; j++){
			if(a[i][j] == 1){
				find_water(i,j);
				count++;
			}
		}
	}
	cout << count;
	return 0;
	
}
void find_water(int x, int y){
	a[x][y] = 0;
	for(int i = -1; i <= 1; i++){
		for(int j = -1; j <= 1; j++){
			if(x+i >= 0 && x+i < n && y+j >= 0 && y+j < m){
				if(a[x+i][y+j] == 1){
					find_water(x+i,y+j);
				}
			}
		}
	}
}

02 无聊的逗(DFS)

题目描述

逗志芃在干了很多事情后终于闲下来了,然后就陷入了深深的无聊中。不过他想到了一个游戏来使他更无聊。他拿出 n n n个木棍,然后选出其中一些粘成一根长的,然后再选一些粘成另一个长的,他想知道在两根一样长的情况下长度最长是多少。

输入

第一行一个数 n n n,表示 n n n个棍子。第二行 n n n个数,每个数表示一根棍子的长度。  n ≤ 15 n \le 15 n15

输出

一个数,最大的长度。

样例
输入

4
1 2 3 1

输出

3

代码
一棵树,每一层代表处理第i个木棍长度。子树1代表放在左边那一堆,子树2表示放在右边那一堆,子树3表示都不放。
选择完毕后比较左右是否相等,以及是否是最优解。

#include
using namespace std;
int a[16] = {0};
int n, maxSum = 0;
void dfs(int k, int left, int right);
int main(void){
	cin >> n;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
	}
	sort(a+1,a+n);
	dfs(1, 0, 0);
	cout << maxSum;
	return 0;
}
void dfs(int k, int left, int right){
	if(k == n + 1){
		if(left == right && maxSum < left){
			maxSum = left;
		}
		return;
	}
	dfs(k + 1, left + a[k], right);	//给左边
	dfs(k + 1, left, right + a[k]); //给右边 
	dfs(k + 1, left, right);//放弃
	return;
}

03 粘木棍(DFS)

题目描述

N N N根木棍,需要将其粘贴成 M M M个长木棍,使得最长的和最短的的差距最小。

输入

第一行两个整数 N , M N,M N,M N , M ≤ 7 N, M \le 7 N,M7
一行 N N N个整数,表示木棍的长度。

输出

一行一个整数,表示最小的差距

样例
输入

3 2
10 20 40

输出

10

代码
枚举每一根木棍,拿出这个木棍以后要进行标记,然后尝试将这个木棍尝试粘到每一根长木棍上,不停的枚举,当小木棍用完以后更新一下最小最大长度差。

#include
using namespace std;
typedef long long int ll;
int n, m, a[8] = {0}, s[8] = {0};
int ans = 999999;
void dfs(int k);
int main(void){
	cin >> n >> m;
	for(int i = 0; i < n; i++){
		cin >> a[i];
	}
	dfs(0);
	cout << ans;
	return 0;
}
// k表示处理第k个木棍
void dfs(int k){
	if(k == n){	// 走到最后了,算出最小值 
		int temp_min = s[0], temp_max = s[0];
		for(int i = 1; i < m; i++){
			temp_min = min(temp_min, s[i]);
			temp_max = max(temp_max, s[i]);
		}
		ans = min(ans, temp_max - temp_min); 
		return;
	}
	// 如果没处理到最后一个,那就枚举到每一组里
	for(int j = 0; j < m; j++){
		s[j] += a[k];
		dfs(k + 1);
		s[j] -= a[k];
	}
	return ;
}

04 车的放置(DFS)

题目描述

在一个 n ∗ n n*n nn的棋盘中,每个格子中至多放置一个车,且要保证任何两个车都不能相互攻击,有多少中放法(车与车之间是没有差别的)

输入

包含一个正整数 n n n n < = 8 n<=8 n<=8

输出

一个整数,表示放置车的方法数

样例
输入

2

输出

7

【样例解释】一个车都不放为1种,放置一个车有4种,放置2个车有2种。

代码
要求车不能放在同一行同一列。
从每行开始判断,对于每一列如果前面的行没有放置过车,他就可以放。同时他也可以这一行不放。
当考虑到棋盘外时,就可以记一次(dfs会遍历所有结果,一次遍历结束就为一种方案)

#include
using namespace std;
typedef long long int ll;
int n;
ll ans = 0; 
bool visited[9] = {0};
void dfs(int k);
int main(void){
	cin >> n;
	dfs(0);
	cout << ans;
	return 0;
}
void dfs(int k){
	if(k == n){
		ans++;
		return ;
	}
	// 选择一列放车 
	for(int i = 0; i < n; i++){
		if(!visited[i]){
			visited[i] = true;
			dfs(k + 1);
			visited[i] = false;
		}
	}
	dfs(k + 1); // 在第k行不选择放车 
	return ;
}

05 Sticks(DFS)

题目描述

George took sticks of the same length and cut them randomly until all parts became at most 50 units long. Now he wants to return sticks to the original state, but he forgot how many sticks he had originally and how long they were originally. Please help him and design a program which computes the smallest possible original length of those sticks. All lengths expressed in units are integers greater than zero.

输入

The input contains blocks of 2 lines. The first line contains the number of sticks parts after cutting, there are at most 64 sticks. The second line contains the lengths of those parts separated by the space. The last line of the file contains zero.

输出

The output should contains the smallest possible length of original sticks, one per line.

样例
输入

9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0

输出

6
5

代码
思绪很乱,dfs一复杂就绕不过来orz(此题纯属照着其他大佬的代码打的,感觉还不是很熟。。

#include
using namespace std;
typedef long long int ll;
int len[65] = {0};
int vis[65] = {0};
int n, num;
bool dfs(int s, int cur, int cnt, int k);
int main(void){
	while(cin >> n && n != 0){
		int sum = 0;
		for(int i = 0; i < n; i++){
			cin >> len[i];
			sum += len[i];
		}
		sort(len, len + n, greater<int>()); // 从大到小 
		
		int ans = sum;
		for(int i = len[0]; i <= sum / 2; i++){  // i原始每棍长度 
			if(sum % i == 0){
				memset(vis, 0, sizeof(vis));
				num = sum / i;
				if(dfs(0, 0, 0, i)){
					ans = i;
					break;
				}
			}
		}
		cout << ans <<endl;
	}
	return 0;
}
bool dfs(int s, int cur, int cnt, int k){
	//s:当前已拼凑的长度, cur:开始位置, cnt:已拼凑的数目, k:单根木棍长度
	if(cnt == num) return true;
	for(int i = cur; i < n; i++){
		if(vis[i] || (i && len[i] == len[i-1] && !vis[i-1])) {
			//使用过 或者 与上一根没用上的棍子长度相同,剪枝
			continue; 
		}
		if(len[i] + s == k) {
			//拼出一根
			vis[i] = 1;
			if(dfs(0, 0, cnt + 1, k)) {
				//继续拼下一根,且成功返回 
				return true;
			} 
			//后面剩余的棍子无法拼成长度为 k的木棍,提前结束 
			vis[i] = 0;
			return false;
		}
		if(len[i] + s < k) {
			//没拼完
			vis[i] = 1;
			if(dfs(len[i] + s, i + 1, cnt, k)) {
				//能成功拼完
				return true; 
			} 
			vis[i] = 0;
			//无法拼成
			if(!s) return false; 
		}
	}
	return false;
}

贪心思想

01 最小字典序

题目描述

给定一个长度为 N N N的字符串 S S S,要构造一个长度为 N N N的字符串 T T T,起初, T T T的一个空的字符串,随后可以反复进行如下任意的操作:

  1. S S S的头部删除一个字符串,添加到 T T T的尾部
  2. S S S的尾部删除一个字符串,添加到 T T T的尾部
    目标是要构造字典序尽量小的字符串 T T T

输入

输入两行
一个整数 N N N代表字符串的长度,接下来输入一个长为 N N N的字符串 ( n ≤ 2000 ) (n \le 2000) n2000,只包含大写字母

输出

输出为一行,字典序最小的字符串,每80个字符添加一个换行

样例
输入

6
ACDBCB

输出

ABCBCD

代码

#include
using namespace std;
char s[2001];
int main(void){
	int n;
	cin >> n;
	for(int i = 0; i < n; i++){
		cin >> s[i];
	}
	int left = 0, right = n-1;
	int count = 0; 
	while(left <= right){
		bool isleft = false;
		for(int i = 0; left + i <= right; i++){
			if(s[left + i] < s[right - i]){
				isleft = true;
				break;
			}else if(s[left + i] > s[right - i]){
				isleft = false;
				break;
			}
		}
		if(isleft){
			cout << s[left++];
		}else{
			cout << s[right--];
		}
		count++;
		// 看题看完整,每80个字符换行 
		if(count == 80){
			cout <<endl;
			count = 0;
		}
	} 
	return 0;
}

简单DP

01 如何编码

题目描述

小南在学习c语言字符串的时候,突然很好奇自己每次输入字符串时需要按的最少键盘次数,可是她太笨了,你能帮帮她吗?(有Caps Lock和Shift两种转换大小写输入的方式 Caps Lock是切换大小写,Shift是使下一个字母切换大小写 初始都为小写状态 结束后也必须是小写状态)

输入

输入 t t t代表有 t t t组。 ( t ≤ 100 ) (t \le 100) (t100)
每组输入一个字符串 ( l e n ≤ 100 ) (len \le 100) (len100)

输出

对于每组测试样例,你必须输出打出这串字符串所需最少的次数

样例
输入

3
Pirates
CSUacm
CSUACM

输出

8
8
8

代码

#include
using namespace std;
int main(void){
	int n;
	string s;
	cin >> n;
	while(n--){
		cin >> s;
		int on[110] = {0};
		int off[110] = {0};
		on[0] = 1;
		for(int i = 1; i <= s.length(); i++){
			if(s[i-1] >= 'A' && s[i-1] <= 'Z'){
				on[i] = min(on[i-1]+1,off[i-1]+2);
				off[i] = min(on[i-1]+2,off[i-1]+2);
			}else{
				on[i] = min(on[i-1]+2,off[i-1]+2);
				off[i] = min(on[i-1]+2,off[i-1]+1);
			}
		}
		cout << min(on[s.length()]+1,off[s.length()]) <<endl;;
	}
	return 0;
} 

02 印章

题目描述

共有 n n n种图案的印章,每种图案的出现概率相同。小 A A A买了 m m m张印章,求小 A A A集齐 n n n种印章的概率。

输入

一行两个正整数 n n n m m m 1 ≤ n , m ≤ 20 1 \le n,m \le 20 1nm20

输出

一个实数 P P P表示答案,保留 4 4 4位小数。

样例
输入

2 3

输出

0.7500

代码
d p [ i ] [ j ] dp[i][j] dp[i][j]:i张印章凑齐j种印章数的概率。

  • i < j i < j i<j,绝对凑不齐,故 d p [ i ] [ j ] = 0 dp[i][j]=0 dp[i][j]=0
  • j = 1 j=1 j=1,即只需要凑一种, d p [ i ] [ j ] = ( 1 n ) i − 1 dp[i][j]={(\frac{1}{n})}^{i-1} dp[i][j]=(n1)i1
  • 其他情况
    • 第i张和前面的i-1张重复了,这一张出自j种里 d p [ i − 1 ] [ j ] ∗ j / n dp[i-1][j] * j / n dp[i1][j]j/n
    • 第i张是一张新的,这一张出自j-1种里 d p [ i − 1 ] [ j − 1 ] ∗ ( n − j + 1 ) / n dp[i-1][j-1] * (n-j+1) / n dp[i1][j1](nj+1)/n
#include
using namespace std;
double dp[21][21] = {0};
//dp[i][j] i张印章凑齐j种印章数的概率。
int main(void){
	int n, m;
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		for(int j = 1; j <= n; j++){
			if(i < j) dp[i][j] == 0; // 次数小于种类时,概率为0 
			else if(j == 1) dp[i][j] = pow(1.0 / n, i - 1);	// 只有一种时 
			else dp[i][j] = dp[i - 1][j - 1] * (n - j + 1) / n + dp[i - 1][j] * j / n;
			// 第i张与前面i-1张重复了,没有增加新种类,j不变   dp[i-1][j] * j / n (这一张出自j种里)
			// 第i张与前面i-1张不重复,增加新种类  dp[i-1][j-1] * (n - j + 1) / n (这一张出自j-1种里)
		}
	}
	cout << fixed << setprecision(4) << dp[m][n];
	return 0;
}

03 逗志芃的危机

题目描述

逗志芃又一次面临了危机。逗志芃的妹子是个聪明绝顶的人,相比之下逗志芃就很菜了。现在她妹子要和他玩一个游戏,这个游戏是这样的:一共有 n n n个数( n n n是偶数)写成一行,然后两个人轮流取数,每次只能从最前面或者最后面取走一个数,全部取完则游戏结束,之后每个人取走的数的和就是每个人的得分。由于逗志芃妹子很厉害,但他又不想输,所以只能找到你了,你要告诉他最多可以得到多少分。(注意,妹子智商是maxlongint所以是不会犯错的,每次的策略必然最优,而且逗志芃是先手)

输入

第一行一个数 n n n,表示有 n n n个数。
第二行就是进行游戏的 n n n个数。
0 < n , m ≤ 1000 00<n,m1000,每个数不超过 10000 10000 10000

输出

一个数,最高得分

样例
输入

2
10 20

输出

20

代码

#include
using namespace std;
typedef long long int ll;
int a[1010] = {0};
int ans[1010][1010] = {0}; //ans[i][j]:在[i...j]范围内, 逗志芃所能获得的最大数的和
int main(void){
	int n;
	cin >> n;
	for(int i = 0; i < n; i++){
		cin >> a[i];
	}
	for(int R = 0; R < n; R++){
		for(int L = R; L >= 0; L--){
			//当只有一个数时,由于逗志芃先手,且共有偶数个数,故该数只能被逗志芃妹妹获取,故逗志芃获取数的和为0
			if(L == R) ans[L][R] = 0;
			//还有偶数个数时逗志芃先手
			else if((R - L) & 1) ans[L][R] = max(a[L] + ans[L + 1][R], a[R] + ans[L][R - 1]);
			//还有奇数个数时逗志芃妹妹先手 
			else ans[L][R] = min(ans[L + 1][R], ans[L][R - 1]);
		}
	}
	cout << ans[0][n-1];
	return 0;
}

树形DP

树形 d p dp dp的主要实现形式是 d f s dfs dfs,在 d f s dfs dfs d p dp dp,主要的实现形式是 d p [ i ] [ j ] [ 0 / 1 ] dp[i][j][0/1] dp[i][j][0/1] i i i是以 i i i为根的子树, j j j是表示在以 i i i为根的子树中选择 j j j个子节点, 0 0 0表示这个节点不选, 1 1 1表示选择这个节点。有的时候 j j j 0 / 1 0/1 0/1这一维可以压掉

01 选课

题目描述

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 N N N 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a a a 是课程 b b b 的先修课即只有学完了课程 a a a,才能学习课程 b b b)。一个学生要从这些课程里选择 M M M 门课程学习,问他能获得的最大学分是多少?

输入

第一行有两个整数 N N N , M M M 用空格隔开。 ( 1 ≤ N ≤ 300 , 1 ≤ M ≤ 300 ) ( 1 \leq N \leq 300 , 1 \leq M \leq 300 ) (1N300,1M300)
接下来的 N N N 行,第 i + 1 i+1 i+1 行包含两个整数 k i k_i ki
s i s_i si, k i k_i ki表示第 i i i门课的直接先修课, s i s_i si表示第 i i i门课的学分。若 k i = 0 k_i=0 ki=0表示没有直接先修课( 1 ≤ k i ≤ N , 1 ≤ s i ≤ 20 1 \le k_i \le N, 1 \le s_i \le 20 1kiN,1si20

输出

只有一行,选 M M M 门课程的最大得分。

样例
输入

7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2

输出

13

代码

#include
using namespace std;
typedef long long int ll;
int dp[310][310] = {0};//以i为根选j门课程的最大学分
int credit[310] = {0};//每门课对应学分 
vector<int> vec[310];//每门课的后继课程 
int n, m;
void dfs(int cur, int pre);
int main(void){
	cin >> n >> m;
	for(int i = 1; i <= n; i++){
		int x;
		cin >> x >> credit[i];
		vec[x].push_back(i);
	}
	dfs(0, -1);
	cout << dp[0][m];
	return 0;
	
}
void dfs(int cur, int pre){
	dp[cur][0] = credit[cur];
	for(int i: vec[cur]){
//		if(i == pre) continue; 这里不需要这一行,因为vec不是邻接表 
		dfs(i, cur);
		for(int j = m; j >= 1; j--){
			for(int k = 0; k < j; k++){
				dp[cur][j] = max(dp[cur][j], dp[cur][j - k - 1] + dp[i][k]);
			}
		}
	}
}

双指针算法

01 最长不重复连续子串

题目描述

输入一个长度为 n ( n ≤ 1 0 6 ) n(n \le 10^6) nn106的序列A,找到一个尽量长的连续子序列 A r A_r Ar~ A p A_p Ap,使得该序列中没有相同的元素。

输入

第一行是一个整数,表示序列的长度 n n n n ≤ 1 0 6 n \le 10^6 n106
第二行有 n n n 个整数,第 i i i 个整数表示序列的第 i i i 个数字 A i A_i Ai 0 ≤ A i ≤ 1 0 6 0 \le A_i \le 10^6 0Ai106

输出

输出一个整数表示最大的重复元素的子序列长度。

样例
输入

7
1 0 1 5 2 0 2

输出

4

代码

  1. 遍历数组a中的每一个元素a[i],对于每一个i,找到j使得双指针[j, i]维护的是以a[i]结尾的最长连续不重复子序列,长度为i - j + 1, 将这一长度与count的较大者更新给count。
  2. 由于[j, i - 1]是前一步得到的最长连续不重复子序列,所以如果[j, i]中有重复元素,一定是a[i],因此右移j直到a[i]不重复为止
  3. 用数组s记录子序列a[j ~ i]中各元素出现次数。
#include
using namespace std;
const int N = 1e6+10;
typedef long long LL;
int a[N] = {0};
int s[N] = {0};
int main(void){
	int n;
	cin >> n;
	for(int i = 0; i < n; i++){
		cin >> a[i];
	}
	int count = 0;
	for(int i = 0, j = 0; i < n; i++){
		s[a[i]]++;
		while(s[a[i]] > 1){
			s[a[j]]--;
			j++;
		}
		count = max(count, i - j + 1);
	}
	cout << count;
	return 0;
}

数论相关

01 互素(欧拉函数)

欧拉函数

就是对于一个正整数 x x x,小于 x x x且和 x x x互质的正整数(包括 1 1 1)的个数,记作 φ ( x ) φ(x) φ(x)
φ ( x ) = x ∑ i = 1 n ( 1 − 1 p i ) \varphi (x) = x\sum_{i=1}^{n} (1-\frac{1}{p_i}) φ(x)=xi=1n(1pi1)
p i p_i pi为x的所有质因数, n n n是不为 0 0 0的整数。
φ ( 1 ) = 1 φ(1)=1 φ(1)=1(唯一和 1 1 1互质的数就是 1 1 1本身)。

题目描述

求小于 n n n且与 n n n互素的整数个数。

输入

整数 n n n n ≤ 1 0 9 n \le 10^9 n109

输出

互素的整数个数

样例
输入

18

输出

6

提示

欧拉函数

代码

#include
using namespace std;
typedef long long ll;
ll eular(ll n);
int main(void){
	ll n;
	cin >> n;
	cout << eular(n);
	return 0;
}
ll eular(ll n){
	ll ans = n;
    for (ll i = 2; i <= n; i++) {
        if (n % i == 0) ans = ans / i * (i - 1);
        while (n % i == 0) n /= i;
    }
    return ans;
}

02 数字统计

按位统计法 O ( l o g 10 n ) O(log_{10}n) O(log10n)

以n=4321, k=2为例

  • 个位:432
    • (0-431)2
    • (432)2
  • 十位: 43×10 + 1×2
    • (0-42)2(0-9)
    • (43)2(0-1)
  • 百位:4×100+1X100
    • (0-3)2(0-99)
    • (4)2(0-99)
  • 千位:1×1000
    • 2(0-999)

当k = 0时,要去掉前导0的情况。
题目描述

请统计某个给定范围 [ L , R ] [L, R] [L,R]的所有整数中,数字 k ( 0 ≤ k ≤ 9 ) k(0 \le k \le 9) k(0k9)出现的次数。
比如给定范围 [ 2 , 22 ] [2, 22] [2,22],数字 2 2 2在数 2 2 2中出现了 1 1 1次,在数 12 12 12中出现 1 1 1次,在数 20 20 20中出现 1 1 1次,在数 21 21 21中出现 1 1 1次,在数 22 22 22中出现 2 2 2次,所以数字 2 2 2在该范围内一共出现了 6 6 6次。

输入

输入三个整数 L , R , k 。 l , r ≤ 1 0 9 L,R,k。l, r \le 10^9 L,R,kl,r109

输出

输出 k k k出现的次数。

样例
输入

2 22 2

输出

6

代码

#include
using namespace std;
int l, r, k;
int countDigit(int x);
int main(void){	
	cin >> l >> r >> k;
	cout << countDigit(r) - countDigit(l-1);
	return 0;
}
int countDigit(int x){
	// 1-x中k出现的个数
	int ans = 0, t = 1, a = x;
	while(x){
		ans += x / 10 * t;
		if(x % 10 > k) ans += t;
		if(x % 10  == k) ans += a % t + 1;
		if(k == 0) ans -= t; //去除前导0 
		x /= 10;
		t *= 10;
	} 
	return ans;
}

03 约数的个数和

统计约数

  • 改分解约数为统计约数
  • f ( x ) f(x) f(x)表示 x x x的约数个数,对于约数 k k k,对 f ( k ) , f ( 2 k ) f(k),f(2k) f(k),f(2k)累加

版本1:原始版本

for(int k = 1; k <= n; k++){
	for(int i = k; i <= n; i += k){
		f[i]++;
	}
}
for(int i = 1; i <= n; i++){
	total += f[i]
}

版本2:优化

for(int k = 1; k <= n; k++){
	for(int i = k; i <= n; i += k){
		total++;
	}
}

版本3:再优化

for(int k = 1; k <= n; k++){
	total += n / k;
}

题目描述

f ( i ) f(i) f(i)代表 n n n的正约数的个数,求 f ( 1 ) + f ( 2 ) + . . . + f ( n ) f(1)+f(2)+...+f(n) f(1)+f(2)+...+f(n)

输入

一个整数 n n n n ≤ 1 0 8 n \le 10^8 n108

输出

约数的个数之和。

样例
输入

5

输出

10

代码

#include
using namespace std;
typedef long long ll;
int main(void){
	ll n;
	cin >> n;
	ll ans = 0;
	for(ll k = 1; k <= n; k++){
		ans += n / k;
	}
	cout << ans;
	return 0;
}

04 数的潜能(需要快速幂)

正整数分解使得乘积最大问题

类型一:自然数互不相同

  • 尽量使得元素是连续的。
  • 如果有多出来的,从后往前均匀分配到各个元素。考虑到一种特殊情况,当多出来的数比前面已有元素的个数大1时(比如8的情况),先给已有元素的最大元素加1,然后再均匀分配到每个元素。

类型二:自然数可以相同
4 = 2 + 2 , 5 = 2 + 3 , 6 = 3 + 3 4=2+2,5=2+3,6=3+3 4=2+25=2+36=3+3
7 = 3 + 2 + 2 , 8 = 3 + 3 + 2 , 9 = 3 + 3 + 3 7=3+2+2,8=3+3+2,9=3+3+3 7=3+2+28=3+3+29=3+3+3

  • 元素不会超过 4 4 4,因为 4 = 2 + 2 4=2+2 4=2+2,又可以转化为 2 2 2的问题,而 5 = 2 + 3 5=2+3 5=2+3 5 < 2 ∗ 3 5<2*3 5<23,所以 5 5 5总能分解成 2 2 2 3 3 3
  • 尽可能多分解出 3 3 3,然后分解出 2 2 2,不要分出 1 1 1
  • 考虑任意一个数,除以 3 3 3之后的结果有以下3种:
    • 能被 3 3 3除断,那么就分解为 3 + 3 + . . . + 3 3+3+...+3 3+3+...+3的情况即可。例如 9 = 3 + 3 + 3 9=3+3+3 9=3+3+3
    • 3 3 3除余 1 1 1,把 1 1 1分给其中一个 3 3 3,得到一个 4 4 4 4 4 4可分为 2 + 2 2+2 2+2
    • 3 3 3除余 2 2 2,分解为 3 + 3 + . . . + 3 + 2 3+3+...+3+2 3+3+...+3+2的情况,例如 11 = 3 + 3 + 3 + 2 11=3+3+3+2 11=3+3+3+2

题目描述

将一个数 N N N分为多个正整数之和,即 N = a 1 + a 2 + a 3 + … + a k N=a1+a2+a3+…+ak N=a1+a2+a3++ak,定义 M = a 1 ∗ a 2 ∗ a 3 ∗ … ∗ a k M=a1*a2*a3*…*ak M=a1a2a3ak N N N的潜能。
给定 N N N,求它的潜能 M M M
由于 M M M可能过大,只需求 M M M 5218 5218 5218取模的余数。

输入

输入共一行,为一个正整数 N N N 1 ≤ N < 1 0 1 8 1 \le N<10^18 1N<1018

输出

输出共一行,为 N N N的潜能 M M M 5218 5218 5218取模的余数。

样例
输入

10

输出

36

代码
快速幂算的是有多少个 3 3 3连乘,注意取余用 5218 5218 5218

#include
using namespace std;
typedef long long int ll;
long long fastPower(long long base, long long power);
int main(void){
	ll n, a, b = 0;
	cin >> n;
	if(n == 1){
		cout << 1;
		return 0;
	}
	if(n % 3 == 0){
		//整除 
		a = n / 3;
	}else if(n % 3 == 1){
		//余1 
		a = n / 3 - 1;
		b = 2;
	}else{
		//余2 
		a = n / 3;
		b = 1; 
	}
	
	ll ans = pow(2, b);
	cout << fastPower(3, a) * ans % 5218;
	return 0;
} 

long long fastPower(long long base, long long power) {
    long long result = 1;
    while (power > 0) {
        if (power & 1) {//此处等价于if(power%2==1)
            result = result * base % 5218;
        }
        power >>= 1;//此处等价于power=power/2
        base = (base * base) % 5218;
    }
    return result;
}

STL的使用

01 map

声明 map m;
插入 m.insert();
   m.insert(map::value_type(s,1));
查找 m.find();
   找到则返回该元素的迭代器,找不到返回m.end();
清空 m.clear();
删除 m.erase();
大小 m.size();
指向头部的迭代器 m.begin();
指向尾部的迭代器 m.end();
迭代器 map::iterator it;

02 priority_queue

声明 priority_queue q;
升序 priority_queue ,greater > q;
降序 priority_queue ,less >q;
判空 p.empty();
队头 p.top();
大小 p.size();
添加 p.push();
删除 p.pop();

03 set和unorder_set比较

逐个比较 set/map unordered_set标记数组
时间复杂度 O ( n ) O(n) O(n) O ( l o g 2 n ) O(log_2n) O(log2n) O ( 1 ) O(1) O(1)
空间复杂度 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( m ) O(m) O(m)

使用set

  • 需要排序的数据
  • 需要通过前序、后序等方式遍历元素或者查找前继后继元素
  • 想要使用binary_search(),lower_bound()和upper_bound()等需要在有序元素上使用的方法
  • 其他平衡二叉树具有而hash表没有的优点

使用unorder_set

  • 仅需要保存互异的元素而不需要排序
  • 只需要获取单个元素而不需要遍历

04 set

05 unorder_set

声明 unordered_set set;
判空 set.empty();
查找 set.find()
插入 set.insert();
删除 set.erase();
清空 set.clear();

输出格式

cout << setw(5) << setfill('0') << a < 补前导0
cout << setprecision(4) << value << endl; 改成4精度,所以输出为12.35
cout << fixed << setprecision(4) << value << endl; 加了fixed意味着是固定点方式显示,所以这里的精度指的是小数位,输出为12.3457

ELSE

01 蚂蚁爬竹竿

题目描述

小南在中南大学信息楼的门口发现了一群蚂蚁正在那里爬竹竿,她发现总共有 n n n只蚂蚁,每只蚂蚁以 1 c m 1cm 1cm的速度在长 L c m Lcm Lcm的竹竿上面爬行。当蚂蚁爬到竹竿的端点时,就会掉落。由于竹竿十分的细,在两只蚂蚁相遇的时候,他们没有办法交错通过,只能各自方向爬回去,小南想考考你,对于每只蚂蚁而言,告诉你它距离竿子左端点的距离为 x i x_i xi,但是不告诉你它的朝向。请计算所有蚂蚁落下竿子所需要的最短距离和最长距离

输入

输入为三行
第一行 L L L代表竹竿的长度 ( n ≤ 1000000 ) (n \le1000000) n1000000
第二行 n n n代表蚂蚁的数量 ( n ≤ 10000 ) (n \le 10000) (n10000)
第三行 x x x的集合,代表每只蚂蚁距离左端点的距离

输出

输出为一行,分别表示蚂蚁下落竿子的最短距离和最长距离

样例
输入

10
3
2 6 7

输出

4 8

代码

#include
using namespace std;
int main(void){
	// 蚂蚁互换灵魂继续爬 
	int L, n, x, maxt = 0, mint = 0;
	cin >> L >> n;
	while(n--){
		cin >> x;
		maxt = max(maxt,max(x,L-x));
		mint = max(mint,min(x,L-x));
	}
	cout << mint << " " << maxt;
	return 0; 
} 

02 两边数–矩阵快速幂

思想:将指数降下来,减少循环次数
3 10 = 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 3^{10}=3*3*3*3*3*3*3*3*3*3 310=3333333333
3 10 = ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) 3^{10}=(3*3)*(3*3)*(3*3)*(3*3)*(3*3) 310=(33)(33)(33)(33)(33)
3 10 = ( 3 ∗ 3 ) 5 3^{10}=(3*3)^5 310=(33)5
3 10 = 9 5 3^{10}=9^5 310=95
3 10 = 9 5 = ( 9 4 ) ∗ ( 9 1 ) 3^{10}=9^5=(9^4)*(9^1) 310=95=(94)(91)
. . . ... ...
. . . ... ...
第一版:无优化

long long fastPower(long long base, long long power) {
    long long result = 1;
    while (power > 0) {
        if (power % 2 == 0) {
            //如果指数为偶数
            power = power / 2;//把指数缩小为一半
            base = base * base % 1000;//底数变大成原来的平方
        } else {
            //如果指数为奇数
            power = power - 1;//把指数减去1,使其变成一个偶数
            result = result * base % 1000;//此时记得要把指数为奇数时分离出来的底数的一次方收集好
            power = power / 2;//此时指数为偶数,可以继续执行操作
            base = base * base % 1000;
        }
    }
    return result;
}

第二版:代码复用优化

long long fastPower(long long base, long long power) {
    long long result = 1;
    while (power > 0) {
        if (power % 2 == 1) {
            result = result * base % 1000;
        }
        power = power / 2;
        base = (base * base) % 1000;
    }
    return result;
}

第三版:位运算优化速度
A = a 156 = a 10011100 A=a^{156}=a^{10011100} A=a156=a10011100
A = a 2 7 ∗ 1 + 2 6 ∗ 0 + 2 5 ∗ 0 + 2 4 ∗ 1 + 2 3 ∗ 1 + 2 2 ∗ 1 + 2 1 ∗ 0 + 2 0 ∗ 0 A=a^{2^7*1+2^6*0+2^5*0+2^4*1+2^3*1+2^2*1+2^1*0+2^0*0} A=a271+260+250+241+231+221+210+200
A = ( a 2 7 ∗ 1 ) ∗ ( a 2 6 ∗ 0 ) ∗ ( a 2 5 ∗ 0 ) ∗ ( a 2 4 ∗ 1 ) ∗ ( a 2 3 ∗ 1 ) ∗ ( a 2 2 ∗ 1 ) ∗ ( a 2 1 ∗ 0 ) ∗ ( a 2 0 ∗ 0 ) A=(a^{2^7}*1)*(a^{2^6}*0)*(a^{2^5}*0)*(a^{2^4}*1)*(a^{2^3}*1)*(a^{2^2}*1)*(a^{2^1}*0)*(a^{2^0}*0) A=(a271)(a260)(a250)(a241)(a231)(a221)(a210)(a200)

long long fastPower(long long base, long long power) {
    long long result = 1;
    while (power > 0) {
        if (power & 1) {//此处等价于if(power%2==1)
            result = result * base % 1000;
        }
        power >>= 1;//此处等价于power=power/2
        base = (base * base) % 1000;
    }
    return result;
}

题目描述

给你一个数字 n n n,求 n n n^n nn最左边的数和最右边的数

输入

首先输入一个整数 t t t,代表有 T T T组样例
然后输入一个整数 n n n n ≤ 1 0 9 n \le10^9 n109

输出

输出 n n n^n nn最高位和最低位的数

样例
输入

1
3

输出

2 7

提示

l o g 10 N N = N ∗ l o g 10 N = M = a . b log10N^N = N*log10N = M = a.b log10NN=Nlog10N=M=a.b ( a (a (a是整数部分, 0. b 0.b 0.b是小数部分 ) ) )
所以 1 0 M = 1 0 a . b = N N 10^M = 10^a.b = N^N 10M=10a.b=NN ( ( ( N N N^N NN a a a位数)
1 0 b 10^b 10b向下取整即为首位数

代码

#include
using namespace std;
typedef long long LL;
long long fastPower(long long base, long long power) ;
int main(void){
	LL t, n;
	cin >> t;
	while(t--){
		cin >> n;
		LL k = n*log10(n);
		double x = pow(10,n*log10(n)-k);
		cout << (LL)x << " " << fastPower(n,n) % 10 <<endl;
	}
	return 0;
}

long long fastPower(long long base, long long power) {
    long long result = 1;
    while (power > 0) {
        if (power & 1) {//此处等价于if(power%2==1)
            result = result * base % 1000;
        }
        power >>= 1;//此处等价于power=power/2
        base = (base * base) % 1000;
    }
    return result;
}

03 搞事情

题目描述

小南很喜欢搞一些比较玄学的事情
她在想,每次都从两个数组中各取一个数,有多少种方式可以使得两者和为 3 3 3或者 7 7 7的倍数

输入

输入一个 n n n m ( 0 < n , m ≤ 100000 ) m(0 < n, m \le 100000) m(0<n,m100000),分别代表数组 a a a b b b的长度
接下来输入两个数组 a , b ( 0 ≤ a [ i ] , b [ i ] < 10000 ) a,b(0 \le a[i], b[i] < 10000) ab(0a[i],b[i]<10000)

输出

对于每组数据输出一行,代表答案。

样例
输入

2 1
1 4
20

输出

2

代码

#include
using namespace std;
const int MAXN = 100000 + 10;
int a1[MAXN] = {0}, a2[MAXN] = {0}, a3[MAXN] = {0};
int b1[MAXN] = {0}, b2[MAXN] = {0}, b3[MAXN] = {0};
int main(void){
	int n,m;
	cin >> n >> m;
	for(int i = 0; i < n; i++){
		int x;
		cin >> x;
		a1[x % 3]++;
		a2[x % 7]++;
		a3[x % 21]++;
	}
	for(int i = 0; i < m; i++){
		int x;
		cin >> x;
		b1[x % 3]++;
		b2[x % 7]++;
		b3[x % 21]++;
	}
	long long sum = 0;
	for(int i = 0; i <= 2; i++){
		for(int j = 0; j <= 2; j++){
			if((i + j) % 3 == 0) sum += (long long)(a1[i] * b1[j]);
		}
	}
	for(int i = 0; i <= 6; i++){
		for(int j = 0; j <= 6; j++){
			if((i + j) % 7 == 0) sum += (long long)(a2[i] * b2[j]);
		}
	}
	for(int i = 0; i <= 20; i++){
		for(int j = 0; j <= 20; j++){
			if((i + j) % 21 == 0) sum -= (long long)(a3[i] * b3[j]);
		}
	}
	cout << sum;
	return 0;
}

03 最大连续和

题目描述

给出一个长度为n的序列 A 1 , A 2 , . . . , A n A_1,A_2,...,A_n A1A2...An,求最大连续和。换句话说,要求找到 1 ≤ i ≤ j ≤ n 1 \le i \le j \le n 1ijn,使得 A i + A i + 1 + . . . + A j A_i+A_{i+1}+...+A_j Ai+Ai+1+...+Aj尽量大。

输入

第一行是一个整数,表示序列的长度 n n n n ≤ 1 0 6 n \le10^6 n106
第二行有 n n n个整数,第 i i i个整数表示序列的第 i i i个数字。 1 0 − 5 ≤ A i ≤ 1 0 5 10^{-5} \le A_i \le 10^5 105Ai105

输出

输出一个整数表示最大连续和。

样例
输入

7
2 -4 3 -1 2 -4 3

输出

4

代码

暴力法 O ( n 3 ) O(n^3) O(n3)

for(int i = 0; i <= n; i++){
	for(int j = i; j <= n; j++){
		ll sum = 0;
		for(int k = i; k <= j; k++){
			sum += a[i];
			ans = max(sum,ans);
		}
	}
} 

前缀和 O ( n 2 ) O(n^2) O(n2)

for(int i = 0; i <= n; i++){
	prefix_sum[i] = prefix_sum[i-1] + a[i];
	for(int j = i; j <= n; j++){
		ll sum = prefix_sum[j] - prefix_sum[i-1];
		ans = max(sum, ans);
	}
} 

前缀和+极值 O ( n ) O(n) O(n)

for(int i = 0; i <= n; i++){
	prefix_sum[i] = prefix_sum[i-1] + a[i];
	ans = max(ans, prefix_sum[i] - min_prefix_sum);
	min_prefix_sum = min(min_prefix_sum, prefix_sum[i]);
} 

分治法 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

ll max_sum(int l, int r){
	if(r - l == 1) return a[l];
	int mid = (l + r) / 2;
	ll ret = max(max_sum(1,mid), max_sum(mid,r));
	ll left_sum = 0;
	for(int i = mid - 1; i >= 1; i--){
		left_sum += a[i];
		ret = max(ret, left_sum);
	}
	ll right_sum = 0;
	for(int i = mid; i < r; i++){
		right_sum += a[i];
		ret = max(ret, right_sum);
	}
	return ret;
}

T ( n ) = 2 T ( n 2 ) + O ( n ) = O ( n l o g 2 n ) T(n)=2T(\frac{n}{2})+O(n)=O(nlog_2n) T(n)=2T(2n)+O(n)=O(nlog2n)

04 女装大佬(前缀和+满足一定条件的最小值)

题目描述

小南是一个女装大佬,他喜欢收藏很多洛丽塔,发现自己的洛丽塔裙子上有一个由 n n n个数组成的数组,恰巧小南的幸运数字是 s s s,小南想求出总和不小于 s s s的子数组长度的最小值,但小南忙着敲代码忙着和小哥哥网恋,请你帮小南解决这个问题。

输入

第一行输入 n n n s s s
n n n代表有多少个数, s s s代表总和值
第二行输入 n n n个正整数
n ( 1 < n ≤ 100000 ) n(1n(1<n100000) s ( 1 < s ≤ 100000000 ) s(1s(1<s100000000)

输出

找到满足要求的子数组的最小长度,如果没有,输出0。

样例
输入

10 15
5 1 3 5 10 7 4 9 2 8

输出

2

代码
此二分法要求数组为单调的!通过每个数都是正整数和要考虑子数组的和,要想到前缀和!sum数组递增。

#include
using namespace std;
typedef long long ll;
ll n, s;
ll sum[100010] = {0};
bool check(ll x);
int main(void){
	cin >> n >> s;
	for(int i = 1; i <= n; i++){
		int x;
		cin >> x;
		sum[i] = sum[i-1] + x;
	}
	ll l = 1, r = n;
	while(l < r){
		ll mid = (l + r) / 2;
		if(check(mid)){
			r = mid;
		}else{
			l = mid + 1;
		}
	}
	if(sum[n] < s){
		cout << 0;
	}else{
		cout << l;
	}
	return 0;
}
bool check(ll x){
	for(int i = 1; i <= n; i++){
		if(i + x - 1 <= n && sum[i + x - 1] - sum[i-1] >= s){
			return true;
		}
	}
	return false;
}

05 礼物(前缀和+满足一定条件的最大值)

题目描述

JiaoShou在爱琳大陆的旅行完毕,即将回家,为了纪念这次旅行,他决定带回一些礼物给好朋友。
在走出了怪物森林以后,JiaoShou看到了排成一排的 N N N个石子。
这些石子很漂亮,JiaoShou决定以此为礼物。
但是这 N N N个石子被施加了一种特殊的魔法。
如果要取走石子,必须按照以下的规则去取。
每次必须取连续的 2 ∗ K 2*K 2K个石子,并且满足前 K K K个石子的重量和小于等于 S S S,后K个石子的重量和小于等于 S S S
由于时间紧迫,Jiaoshou只能取一次。
现在JiaoShou找到了聪明的你,问他最多可以带走多少个石子。

输入

第一行两个整数 N N N S S S
第二行 N N N个整数,用空格隔开,表示每个石子的重量。

对于 20 % 20\% 20%的数据: N ≤ 1000 N \le 1000 N1000
对于 70 % 70\% 70%的数据: N ≤ 100 , 000 N \le 100,000 N100,000
对于 100 % 100\% 100%的数据: N ≤ 1000 , 000 , S ≤ 1 0 1 2 N \le 1000,000,S \le 10^12 N1000,000S1012,每个石子的重量小于等于 1 0 9 10^9 109,且非负

输出

第一行输出一个数表示JiaoShou最多能取走多少个石子。

样例
输入

8 3
1 1 1 1 1 1 1 1

输出

6

代码

#include
using namespace std;
typedef long long int ll;
const int MaxSize = 1e6+5;
ll val[MaxSize] = {0}, a[MaxSize] = {0};
ll n, s;
bool check(int mid);
int main(void){
	cin >> n >> s;
	// 求前缀和 
	for(ll i = 1; i <= n; i++){
		cin >> a[i];
		val[i] = val[i - 1] + a[i];
	}
	int l = 1, r = n;
	while(l < r){
		int mid = (l + r + 1) / 2;
		if(check(mid)){
			l = mid;
		}else{
			r = mid - 1;
		}
	}
	cout << 2 * r;
	return 0;
}
bool check(int mid){
	for (int i = mid; i <= n - mid; i++){
        if (val[i] - val[i - mid] <= s && val[i + mid] - val[i] <= s){
            return true;
        }
    }
    return false;
}

06 十六进制转八进制

问题描述

给定 n n n个十六进制正整数,输出它们对应的八进制数。

输入

输入的第一行为一个正整数 n ( 1 ≤ n ≤ 10 ) n (1 \le n \le10) n1n10
接下来 n n n行,每行一个由 0 ∼ 9 0 \sim 9 09、大写字母 A ∼ F A \sim F AF组成的字符串,表示要转换的十六进制正整数,每个十六进制数长度不超过 100000 100000 100000

输出

输出 n n n行,每行为输入对应的八进制正整数。

【注意】
  输入的十六进制数不会有前导 0 0 0,比如 012 A 012A 012A
  输出的八进制数也不能有前导 0 0 0

样例
输入

2
39
123ABC

输出

71
4435274

提示

先将十六进制数转换成某进制数,再由某进制数转换成八进制。

代码
关键在于如果转十进制会很长,不如转二进制。
1个十六进制字符对应4个二进制字符;三个二进制字符对应1个8进制字符。

#include
using namespace std;
string d16[16]={"0000","0001","0010","0011","0100","0101","0110","0111","1000","1001","1010","1011","1100","1101","1110","1111"};
int main(void){
	int n;
	cin >> n;
	while(n--){
		string s16, s2, s8;
		cin >> s16;
		for(int i = 0; i < s16.length(); i++){
			int ans = 0;
			if(s16[i] >= 'A'){
				ans = s16[i] - 'A' + 10;
			}else{
				ans = s16[i] - '0';
			}
			s2 += d16[ans];
		}
		if(s2.length() % 3 == 1){
			s2 = "00" + s2;
		}else if(s2.length() % 3 == 2){
			s2 = '0' + s2;
		}
		for(int i = 0; i < s2.length() - 2; i += 3){
			int ans = (s2[i] - '0') * 4 + (s2[i+1] - '0') * 2 + (s2[i+2] - '0');
			s8 += ans + '0';
		}
		//去除前导0
		bool flag = true;
		int t = 0;
		for(int i = 0; i < s8.length(); i++){
			if(flag && s8[i] != '0'){
				flag = false;
				break;
			}
			t++;
		} 
		cout << s8.substr(t);
		if(n != 0){
			cout <<endl;
		}
	}
	return 0;
}

07 阶乘和(高精度乘法和加法)

题目描述

用高精度计算出 S = 1 ! + 2 ! + 3 ! + ⋯ + n ! ( n ≤ 50 ) S = 1! + 2! + 3! + \cdots + n!(n \le 50) S=1!+2!+3!++n!n50
其中 ! ! ! 表示阶乘,定义为 n ! = n × ( n − 1 ) × ( n − 2 ) × ⋯ × 1 n!=n\times (n-1)\times (n-2)\times \cdots \times 1 n!=n×(n1)×(n2)××1。例如, 5 ! = 5 × 4 × 3 × 2 × 1 = 120 5! = 5 \times 4 \times 3 \times 2 \times 1=120 5!=5×4×3×2×1=120

输入

一个正整数 n n n。对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 50 1 \le n \le 50 1n50

输出

一个正整数 S S S,表示计算结果。

样例
输入

3

输出

9

代码

#include
using namespace std;
typedef long long int ll;
int a[100] = {0};
int b[100] = {0};
int c[100] = {0};
int sum[100] = {0};
int main(void){
	int n;
	cin >> n;
	a[0] = 1; 
	for(int x = 1; x <= n; x++){
		//把x换到b数组里 
		int temp = x, i = 0;
		while(temp){
			b[i++] = temp % 10;
			temp /= 10;
		}
		//b数组和a数组相乘
		memset(c,0,sizeof(c));
		for(i = 0; i < 99; i++){
			for(int j = 0; j <= i; j++){
				c[i] += a[j] * b[i - j];
			}
			if(c[i] >= 10){
				c[i + 1] += c[i] / 10;
				c[i] %= 10; 
			}
		}
		//c数组加到sum数组上
		for(i = 0; i < 99; i++){
			sum[i] += c[i];
			if(sum[i] >= 10){
				sum[i + 1] += sum[i] / 10;
				sum[i] %= 10;
			}
		}
		//c数组赋值给a数组
		for(i = 0; i < 100; i++){
			a[i] = c[i];
		} 
	}
	int length = 0;
	for(int i = 99; i >= 0; i--){
		if(sum[i] != 0){
			break;
		}else{
			length++;
		}
	}
	for(int i = 99- length; i >= 0; i--){
		cout << sum[i];
	}
	return 0;
	
}

08 幂次方(位运算)

题目描述

任何一个正整数都可以用 2 2 2 的幂次方表示。例如 137 = 2 7 + 2 3 + 2 0 137=2^7+2^3+2^0 137=27+23+20
同时约定方次用括号来表示,即 a b a^b ab 可表示为 a ( b ) a(b) a(b)
由此可知, 137 137 137 可表示为 2 ( 7 ) + 2 ( 3 ) + 2 ( 0 ) 2(7)+2(3)+2(0) 2(7)+2(3)+2(0)
进一步:
7 = 2 2 + 2 + 2 0 7= 2^2+2+2^0 7=22+2+20 ( 2 1 2^1 21 2 2 2 表示),并且 3 = 2 + 2 0 3=2+2^0 3=2+20
所以最后 137 137 137 可表示为 2 ( 2 ( 2 ) + 2 + 2 ( 0 ) ) + 2 ( 2 + 2 ( 0 ) ) + 2 ( 0 ) 2(2(2)+2+2(0))+2(2+2(0))+2(0) 2(2(2)+2+2(0))+2(2+2(0))+2(0)
又如 1315 = 2 10 + 2 8 + 2 5 + 2 + 1 1315=2^{10} +2^8 +2^5 +2+1 1315=210+28+25+2+1
所以 1315 1315 1315 最后可表示为 2 ( 2 ( 2 + 2 ( 0 ) ) + 2 ) + 2 ( 2 ( 2 + 2 ( 0 ) ) ) + 2 ( 2 ( 2 ) + 2 ( 0 ) ) + 2 + 2 ( 0 ) 2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0) 2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)

输入

一行一个正整数 n n n。对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 2 × 10 4 1 \le n \le 2 \times {10}^4 1n2×104

输出

符合约定的 n n n 0 , 2 0, 2 0,2 表示(在表示中不能有空格)。

样例
输入

1315

输出

2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)

代码

#include
using namespace std;
typedef long long int ll;
void cal(int x);
int main(void){
	int n;
	cin >> n;
	cal(n);
	return 0;
}

void cal(int x){
	vector<int> bit; //存储位数 
	int i = 0;
	while(x){ //对数字进行位移扫描,如果为1则存储位数
		if(x & 1) bit.emplace_back(i); // x的第一位如果,是1则会返回1,为0则会返回0
		x >>= 1; //向右位移一位
		i++;
	} 
	//输出
	for(i = bit.size() - 1; i >= 0; i--){
		if(bit[i] != 1){
			cout << "2(";
			if(bit[i] == 0) cout << "0"; //如果位数为零,则输出格式 2(0)
			else cal(bit[i]);
			cout << ")";
		}else cout << "2"; //如果为一,则输出格式 2
		cout << (i == 0? "" : "+");
	}
}

一些做题的提示

  1. 蓝桥杯系统不支持to_string()
  2. 声明后一定要赋初值!万一在后面有一种情况不需要对某个参数赋值就会出错!!【such as 数的潜能】
  3. sort函数 greater() 由大到小 \quad less() 由小到大
  4. 多组输入 while(cin >> n && cin >> m){...}

你可能感兴趣的:(算法,c++,数据结构)