AcWing-算法提高课【合集】

优质宝藏题解

动态规划

数字三角形

特征:
①从左上角到右下角
②走一条路或是n条路
③求最大值或最小值

在这里插入图片描述

1015. 摘花生

Hello Kitty想摘点花生送给她喜欢的米老鼠。

她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。

地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。

Hello Kitty只能向东或向南走,不能向西或向北走。

问Hello Kitty最多能够摘到多少颗花生。
在这里插入图片描述

输入格式
第一行是一个整数T,代表一共有多少组数据。
接下来是T组数据。
每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。
每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。
输出格式
对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。
数据范围

1≤T≤100
1≤R,C≤100,
0≤M≤1000

输入样例:

2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
输出样例:

8
16

简记:(从1开始,求max)   最后一步划分
	所有转移情况:[i][j]为[i-1][j] 或 [i][j-1] 转移(同时加每格子价值w[i][j])
		f[i][j] = max(f[i-1][j],f[i][j-1])+w[i][j];
 		printf("%d", f[n][m] ) ; 下标从1开始
#include <iostream> 
#include <algorithm> //需记库 
using namespace std;

const int N = 105;

int n,m;
int w[N][N],f[N][N];

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        
        scanf("%d%d",&n,&m);
        for(int i = 1;i <= n;i++)
            for(int j = 1;j <= m;j++)
                scanf("%d",&w[i][j]);
            
        for(int i = 1;i <= n;i++)
            for(int j = 1;j <= m;j++)
                f[i][j] = max(f[i-1][j],f[i][j-1])+w[i][j];
            
        printf("%d\n",f[n][m]);
    }
    return 0;
}

1018.最低通行费

在这里插入图片描述
一个商人穿过一个 N×N 的正方形的网格,去参加一个非常重要的商务活动。
他要从网格的左上角进,右下角出。
每穿越中间 1 个小方格,都要花费 1 个单位时间。
商人必须在 (2N−1)个单位时间穿越出去。 【穿过边长时耗时间1( 指左上走到右下的最小花费)】
而在经过中间的每个小方格时,都需要缴纳一定的费用。
这个商人期望在规定时间内用最少费用穿越出去。
请问至少需要多少费用?
注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。
输入格式
第一行是一个整数,表示正方形的宽度 N。
后面 N 行,每行 N 个不大于 100 的正整数,为网格上每个小方格的费用。
输出格式
输出一个整数,表示至少需要的费用。
数据范围
1≤N≤100

输入样例:
5
1 4 6 8 10
2 5 7 15 17
6 8 9 18 20
10 11 12 19 21
20 23 25 29 33

输出样例:
109

样例解释:
样例中,最小值为 109=1+2+5+7+9+12+19+21+33。

简记:(从1开始,求min【特判边界均INF,导致无法更新最小值】,初始化INF = 0x3f3f3f3f )【公式:数字三角形】

数字三角形但属性min-需特判边界

数字三角形和摘花生初始化边界就为0, 不影响max转移判断
此题与摘花生转移方式相同【仅属性不同:min】:边界初始值为0,会影响第一行和列都变0:需解决【把边界初始化INF】
总结:求min,但转移过程用到边界初始值0,第一行(列)会min变0

#include<cstdio>
#include<algorithm>

using  namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int w[N][N];
int f[N][N];

int main()
{
    int L;
    scanf("%d", &L); 
    for(int i = 1; i <= L; i++)
        for(int j = 1; j <= L; j++)
            scanf("%d", &w[i][j]);
	
    for(int i = 1; i <= L; i++) //处理边界初始INF,用到边界转移不影响第一行(列)
        f[0][i] = f[i][0] = INF;
    
    for(int i = 1; i <= L; i++)
        for(int j = 1; j <= L; j++) 
            if(i == 1 && j == 1) f[i][j] = w[i][j]; //处理边界 出发点的值固定先初始化: w[i][j]
            else f[i][j] = min(f[i][j - 1], f[i - 1][j]) + w[i][j];
            
    printf("%d\n", f[L][L]);
    
    return 0;
}

y总判边界:



#include<bits/stdc++.h>

#include<algorithm>
using namespace std;

const int N = 110,INF = 1e9;

int n;
int w[N][N];
int f[N][N];

int main()
{
	scanf("%d",&n);
	
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= n;j++)
			scanf("%d",&w[i][j]);
	//f[i][0]和f[0][j] = 0  min判断边界会影响最小值【第一行或列会变成0】
	for(int i = 1;i <= n;i++)  
		for(int j = 1;j <= n;j++)//因为从1开始,边界判断  
			if(i == 1 && j == 1) f[i][j] = w[i][j];  //特判左上角【出发点不需要转移,固定值】 
			else
			{
				f[i][j] = INF;//边初始值边更新(导致下面分类,边界f[0][第一列]=f[第一行][0] = 0)
				if(i > 1)f[i][j] = min(f[i][j],f[i - 1][j] + w[i][j]);//只有不在第一行的时候,才可以从上面过来 
				if(j > 1)f[i][j] = min(f[i][j],f[i][j - 1] + w[i][j]);//只有不在第一列的时候,才可以从左边过来 
			}
			
	printf("%d\n",f[n][n]);		
		
		
	return 0;
}




1027. 方格取数

设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:

在这里插入图片描述

某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点。
在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。
输入格式
第一行为一个整数N,表示 N×N 的方格图。
接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。
行和列编号从 1 开始。
一行“0 0 0”表示结束。
输出格式
输出一个整数,表示两条路径上取得的最大的和。
数据范围
N < = 10

输入样例:
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0

输出样例:
67

(NOPI难度)
朴素:(1,1),(1,1)分别走到(i1,j1), (i2,j2)的路径最大值 ,如何处理同一个格子不能被重复选择 bool标记
只有在 i1+j1 == i2+ j2 时,两条路径才可能重合 (需保证有重合时,值只被计算一次)
f[k,i1,i2] 表示(1,1)—>(i1,k-i1) , (1,1)—>(i2,k-i2) 的max 【k表示两条路径当前走到的格子的横纵坐标之和】
划分:(右,下)*(右,下) : 四种选法 (走两次:每个格子只能算一次值)

简记:输入:while(cin >> a >> b >> c, a|| b || c) w[a][b] = c;
 f[k,i1,i2] : 分重合 :w[i1,j1] ,不重合 :w[i1,j1] + w[i2,j2]      (k 到2n ,i1到n,i2到n,三重遍历)
		int j1 = k - i1,j2 = k - i2; 【在[1,n]内遍历】
				int t = w[i1][j1];
				int &x = f[k][i1][i2]; //引用,用别名表示(太长了 !!!,把答案取个别名)
				if(i1 != i2) t+= w[i2][j2]; //不重复
					 //枚举四种方向分法(右,下)* (右,下)
				 x = max(x,f[k-1][i1-1][i2-1] + t);
				 x = max(x,f[k-1][i1][i2-1] + t);
				 x = max(x,f[k-1][i1-1][i2-1] + t);
				 x = max(x,f[k-1][i1][i2] + t); //两条都往右 

#include<bits/stdc++.h>
#include<algorithm>

using namespace std;

const int N = 15;

int n;
int w[N][N];
int f[N*2][N][N];

int main()
{
	scanf("%d",&n);
	
	int a,b,c;
	while(cin >> a >> b >> c, a|| b || c) w[a][b] = c;
	
	for(int k = 2;k <= n+n;k++)
		for(int i1 = 1;i1 <= n;i1++)
			for(int i2 = 1;i2 <= n;i2++)
			{
				int j1 = k - i1,j2 = k - i2;
				if(j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n)
				{
					int t = w[i1][j1];
					int &x = f[k][i1][i2]; //引用,用别名表示(太长了 
					if(i1 != i2) t+= w[i2][j2]; //不重复
					 //枚举四种方向分法(右,下)* (右,下)
					 x = max(x,f[k-1][i1-1][i2-1] + t);
					 x = max(x,f[k-1][i1][i2-1] + t);
					 x = max(x,f[k-1][i1-1][i2-1] + t);
					 x = max(x,f[k-1][i1][i2] + t); //两条都往右 
				}
			 } 
	
	printf("%d\n",f[n + n][n][n]);	
		
	return 0;
}

最长上升子序列LIS

在这里插入图片描述

895基础题:
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式
第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式
输出一个整数,表示最大长度。

数据范围
1≤N≤1000,
−10 ^ 9≤数列中的数≤10 ^ 9

输入样例:
7
3 1 2 1 8 5 6

输出样例:
4

简记:分类:算出以a[i] 结尾的LIS , 每个a[i]结尾的最大值存在f[i]
比较f不同结尾的LIS长度: res = max(res , f[i] )    i ∈[1,n]
	for(int i = 1;i <= n;i++)
	{
		f[i] = 1;
		for(int j = 1;j < i;j ++)     
			if(a[j] < a[i])       一个比a[j] 大的值,比f[j]多了一个长度: a[i] 的max对应 f[i] 
				f[i] = max(f[i] , f[j] + 1);   
	}
	int res = 0;
	for(int i = 1;i <= n;i++) res = max(res,f[i]);

#include<bits/stdc++.h>

#include<algorithm>
using namespace std;

const int N = 1010;

int n;
int a[N],f[N];
int main()
{
	scanf("%d",&n);
	
	for(int i = 1;i <= n;i++) scanf("%d",&a[i]);	
	
	for(int i = 1;i <= n;i++)
	{
		f[i] = 1;
		for(int j = 1;j < i;j ++)
			if(a[j] < a[i])
				f[i] = max(f[i] , f[j] + 1);
	}
	
	int res = 0;
	for(int i = 1;i <= n;i++) res = max(res,f[i]);
	printf("%d\n",res);	
		
	return 0;
}


1017. 怪盗基德的滑翔翼

怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。

而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。

有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。

不得已,怪盗基德只能操作受损的滑翔翼逃脱。

假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。

初始时,怪盗基德可以在任何一幢建筑的顶端。

他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。

因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。

他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。

请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?

输入格式
输入数据第一行是一个整数K,代表有K组测试数据。

每组测试数据包含两行:第一行是一个整数N,代表有N幢建筑。第二行包含N个不同的整数,每一个对应一幢建筑的高度h,按照建筑的排列顺序给出。

输出格式
对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。

数据范围
1≤K≤100,
1≤N≤100,
0<h<10000
输入样例:
3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10
输出样例:
6
6
9
在这里插入图片描述

方向确定就只能往一个方向跳(左/右)
如图:起点a[i] ,最长距离就是以a[i] 结尾的最长上升子序列
【图形:先上升再下降(则右边往左看为逆LIS ,左<—右 ,逆序LIS (起点n ,i- -,j- -))

简记:(正向+反向求解LIS ,比较得max)逆序:  起点n ,i--,j-- ,i,j意义与正向一样不变
	//不用res=0, 正反方向取max 
	for(int i = n; i ;i--) //i > 0
	{
		f[i] = 1;
		for(int j = n;j > i;j --)
			if(a[i] > a[j])
				f[i] = max(f[i] , f[j] + 1);  
		res = max(res,f[i]); //算出一个f[i]就比较一次 
	}
#include<bits/stdc++.h>

#include<algorithm>
using namespace std;

const int N = 1010;

int n,T;
int a[N],f[N];
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
	
		for(int i = 1;i <= n;i++) scanf("%d",&a[i]);	
		
		//正向求解LIS
		int res = 0;
		for(int i = 1;i <= n;i++)
		{
			f[i] = 1;
			for(int j = 1;j < i;j ++)
				if(a[j] < a[i])
					f[i] = max(f[i] , f[j] + 1);  
			res = max(res,f[i]); //算出一个f[i]就比较一次 
		}
		//不用res=0, 正反方向取max 
		for(int i = n; i ;i--) //i > 0
		{
			f[i] = 1;
			for(int j = n;j > i;j --)
				if(a[i] > a[j])
					f[i] = max(f[i] , f[j] + 1);  
			res = max(res,f[i]); //算出一个f[i]就比较一次 
		}
		
		printf("%d\n",res);	
	}
		
	return 0;
}


1014.登山

题目描述:

五一到了,ACM队组织大家去登山观光,队员们发现山上一个有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。

同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。

队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?

输入格式

第一行包含整数N,表示景点数量。

第二行包含N个整数,表示每个景点的海拔。

输出格式

输出一个整数,表示最多能浏览的景点数。

数据范围

2≤N≤1000

输入样例:

8
186 186 150 200 160 130 197 220
输出样例:

4
在这里插入图片描述

按编号递增顺序浏览 --> 必须是子序列
先严格上升再严格下降
一旦开始下降就不能上升了
分类:以a[k]为极值点(转折)的子序列的max(正+反-1:有一个共同重叠点)

简记: a[k]的max:上升存f[i],下降存g[i] ,res = max(res  , f[i] + g[i] - 1)

#include<bits/stdc++.h>

#include<algorithm>
using namespace std;

const int N = 110;

int n,T;
int a[N];
int g[N],f[N];
int main()
{

		scanf("%d",&n);
		for(int i = 1;i <= n;i++) scanf("%d",&a[i]);	
		
		//正向求解LIS存到f[i] 
		for(int i = 1;i <= n;i++)
		{
			f[i] = 1;
			for(int j = 1;j < i;j ++)
				if(a[j] < a[i])
					f[i] = max(f[i] , f[j] + 1);  
		}
		
		//反向求解LIS 存到g[i] 
		for(int i = n;i;i--)
		{
			g[i] = 1;
			for(int j = n;j > i;j--)
				if(a[i] > a[j])
				g[i] = max(g[i], g[j] + 1);
		} 
		
		int res = 0;
		for(int i = 1;i <= n;i++) res = max(res , f[i] + g[i] - 1);
		printf("%d\n",res);	
		
	return 0;
}

482.合唱队形

题目描述:

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK,  则他们的身高满足T1<…Ti+1>…>TK(1≤i≤K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入格式

输入的第一行是一个整数N,表示同学的总数。

第二行有n个整数,用空格分隔,第i个整数Ti是第i位同学的身高(厘米)。

输出格式

输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

数据范围

2≤N≤100,
130≤Ti≤230

输入样例:

8
186 186 150 200 160 130 197 220
输出样例:

4

最少去掉多少个能变成严格单调上升再下降 :
== 总人数 - (正向LIS+反向LIS) = (n - res ) = n(n - max(f[i]+g[i]-1))

简记:(代码和1014.登山一样,仅输出变成n-res)

#include<bits/stdc++.h>

#include<algorithm>
using namespace std;

const int N = 110;

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

		scanf("%d",&n);
		for(int i = 1;i <= n;i++) scanf("%d",&a[i]);	
		
		//正向求解LIS存到f[i] 
		for(int i = 1;i <= n;i++)
		{
			f[i] = 1;
			for(int j = 1;j < i;j ++)
				if(a[j] < a[i])
					f[i] = max(f[i] , f[j] + 1);  
		}
		
		//反向求解LIS 存到g[i] 
		for(int i = n;i;i--)
		{
			g[i] = 1;
			for(int j = n;j > i;j--)
				if(a[i] > a[j])
				g[i] = max(g[i], g[j] + 1);
		} 
		
		int res = 0;
		for(int i = 1;i <= n;i++) res = max(res , f[i] + g[i] - 1);
		printf("%d\n",n - res);	
		
	return 0;
}


1012. 友好城市

Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。

北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。

每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。

编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。

输入格式
第1行,一个整数N,表示城市数。

第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。

输出格式
仅一行,输出一个整数,表示政府所能批准的最多申请数。

数据范围
1≤N≤5000,
0≤xi≤10000
输入样例:
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
输出样例:
4
在这里插入图片描述

自变量与因变量:x,y为一对友好城市坐标【对应的(x,y)才可建造一座桥
(先把一岸的x排序,然后遍历对岸y的LIS ,即为res = max(res,y_LIS))

简记:先坐标排序PII q    : q[i].first = x (编号排序)  , q[i].second  = y (值比较)    下标从0开始  
可以直接 : sort(q, q+ n)  按q[i].first  排序

#include<bits/stdc++.h>

#include<algorithm>
using namespace std;

typedef pair<int ,int > PII;
const int N = 5010;

int n;
PII q[N];  //坐标(x,y)    [编号排序,值比较]
int f[N];

int main()
{

		scanf("%d",&n);
		for(int i = 0;i < n;i++) scanf("%d%d",&q[i].first,&q[i].second);	
		sort(q,q+n);  //默认first,横坐标排序     [纵坐标为值]         
		
		int res = 0;
		for(int i = 0;i < n;i++)
		{
			f[i] = 1;
			for(int j = 0;j < i;j++)
				if(q[i].second > q[j].second)
					f[i] = max(f[i], f[j] + 1);
			res = max( res , f[i]);	
		} 
		
	printf("%d\n",res);	
		
	return 0;
}

1016. 最大上升子序列和

一个数的序列 bi,当 b1<b2<…<bS 的时候,我们称这个序列是上升的。

对于给定的一个序列(a1,a2,…,aN),我们可以得到一些上升的子序列(ai1,ai2,…,aiK),这里1≤i1<i2<…<iK≤N。

比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。

这些子序列中和最大为18,为子序列(1,3,5,9)的和。

你的任务,就是对于给定的序列,求出最大上升子序列和。

注意,最长的上升子序列的和不一定是最大的,比如序列(100,1,2,3)的最大上升子序列和为100,而最长上升子序列为(1,2,3)。

输入格式
输入的第一行是序列的长度N。

第二行给出序列中的N个整数,这些整数的取值范围都在0到10000(可能重复)。

输出格式
输出一个整数,表示最大上升子序列和。

数据范围
1≤N≤1000
输入样例:
7
1 7 3 5 9 4 8
输出样例:
18

在这里插入图片描述

	简记:(LIS算法 ,把f[j]+1改成 f[j]+ a[i] 就可求子序列和的最大值)

#include<bits/stdc++.h>

#include<algorithm>
using namespace std;

const int N = 1010;

int n;
int a[N];
int f[N];
int main()
{

		scanf("%d",&n);
		for(int i = 1;i <= n;i++) scanf("%d",&a[i]);	
		
		//正向求解LIS存到f[i] 
		for(int i = 1;i <= n;i++)
		{
			f[i] = 1;
			for(int j = 1;j < i;j ++)
				if(a[j] < a[i])
					f[i] = max(f[i] , f[j] + a[i]);  
		}
	
		int res = 0;
		for(int i = 1;i <= n;i++) res = max(res , f[i]);
		printf("%d\n",n - res);	
		
	return 0;
}

1010.拦截导弹

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式
共一行,输入导弹依次飞来的高度。

输出格式
第一行包含一个整数,表示最多能拦截的导弹数。

第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。

数据范围
雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。

输入样例:
389 207 155 300 299 170 158 65 【加点才结束…】

输出样例:
6
2

思路:hhh


#include<bits/stdc++.h>

#include<algorithm>
using namespace std;

const int N = 1010;

int n;
int q[N];    //a[i]
int f[N],g[N]; 
int main()
{
		while( cin >> q[n] ) n++;
		
		int res = 0;
		for(int i = 0;i < n;i++)
		{
			f[i] = 1;
			for(int j = 0;j < i;j ++)
				if(q[j] >= q[i])
					f[i] = max(f[i] , f[j] + 1);  
			res = max(res ,f[i]);
		}
	
		printf("%d\n",res);	
		
		int cnt = 0;
     	for(int i = 0;i < n; i++)
	     {
	         int k = 0;
	         while(k < cnt && g[k] < q[i]) k++;
	         g[k]=q[i];
	         if(k >= cnt) cnt++;
	     }
		
		printf("%d\n",cnt);	
		
	return 0;
}

187. 导弹防御系统

为了对抗附近恶意国家的威胁,R 国更新了他们的导弹防御系统。

一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。

例如,一套系统先后拦截了高度为 3 和高度为 4 的两发导弹,那么接下来该系统就只能拦截高度大于 4 的导弹。

给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。

输入格式
输入包含多组测试用例。

对于每个测试用例,第一行包含整数 n,表示来袭导弹数量。

第二行包含 n 个不同的整数,表示每个导弹的高度。

当输入测试用例 n=0 时,表示输入终止,且该用例无需处理。

输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。

数据范围
1≤n≤50
输入样例:
5
3 5 2 4 1
0
输出样例:
2
样例解释
对于给出样例,最少需要两套防御系统。

一套击落高度为 3,4 的导弹,另一套击落高度为 5,2,1 的导弹。

贪心
选择1:接在现有的某个子序列之后
选择2:创建1个新的系统
解释(代码java):min(上升+下降 的LIS数量)

272. 最长公共上升子序列

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。

小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列 A 和 B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。

不过,只要告诉奶牛它的长度就可以了。

数列 A 和 B 的长度均不超过 3000。

输入格式
第一行包含一个整数 N,表示数列 A,B 的长度。

第二行包含 N 个整数,表示数列 A。

第三行包含 N 个整数,表示数列 B。

输出格式
输出一个整数,表示最长公共上升子序列的长度。

数据范围
1≤N≤3000,序列中的数字均不超过 231−1。

输入样例:
4
2 2 1 3
2 1 2 3
输出样例:
2

背包

423. 采药

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。

为此,他想拜附近最有威望的医师为师。

医师为了判断他的资质,给他出了一个难题。

医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式
输入文件的第一行有两个整数T和M,用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。

接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式
输出文件包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

数据范围
1≤T≤1000,
1≤M≤100
输入样例:
70 3
71 100
69 1
1 2
输出样例:
3

1024. 装箱问题

有一个箱子容量为V ,同时有n 个物品,每个物品有一个体积(正整数)。要求n 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

输入格式:
第一行是一个整数V ,表示箱子容量。第二行是一个整数n ,表示物品数。接下来n 行,每行一个正整数(不超过10000 ),分别表示这n 个物品的各自体积。

输出格式:
一个整数,表示箱子剩余空间。

数据范围:
0 < V ≤ 20000
0 < n ≤ 30

1022. 宠物小精灵之收服

宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。

一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。

小智也想收服其中的一些小精灵。

然而,野生的小精灵并不那么容易被收服。

对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。

当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。

当小智的精灵球用完时,狩猎也宣告结束。

我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。

如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。

小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。

现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。

请问,小智该如何选择收服哪些小精灵以达到他的目标呢?

输入格式
输入数据的第一行包含三个整数:N,M,K,分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。

之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。

输出格式
输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。

数据范围
0<N≤1000,
0<M≤500,
0<K≤100

输入样例1:
10 100 5
7 10
2 40
2 50
1 20
4 20

输出样例1:
3 30

6. 多重背包问题 III

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

####输入格式
第一行两个整数,N,V (0<N≤1000, 0<V≤20000),用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N≤1000
0<V≤20000
0<vi,wi,si≤20000
提示
本题考查多重背包的单调队列优化方法。

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
1
2
3
4
5
输出样例:
10

8. 二维费用的背包问题

每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

输入格式
第一行三个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。

接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N≤1000
0<V,M≤100
0<vi,mi≤100
0<wi≤1000

输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6

输出样例:
8

简记:01 背包+重量限制,再多开一维加优化:
	f[j][k] = max (f[j - v[i]][k - m[i]] + w[i], f[j][k]);  满足两个限制,V,M均从大往小开始转移
// 01 背包+重量限制,再多开一维。
//即三维,同01背包压缩优化成二维

#include <bits/stdc++.h>
using namespace std;

const int N = 1e3 + 10;

int n, V, M; //种类,体积,重量
int v[N], m[N], w[N], f[N][N]; 

int main () {
    cin >> n >> V >> M;
    for (int i = 1; i <= n; i ++) {
        cin >> v[i] >> m[i] >> w[i];//体积,重量,价值
    }
    for (int i = 1; i <= n; i ++)
        for (int j = V; j >= v[i]; j --) //均从大往小开始转移
            for (int k = M; k >= m[i]; k --)
                f[j][k] = max (f[j - v[i]][k - m[i]] + w[i], f[j][k]);//两个限制均要满足
    cout << f[V][M];
    
    return 0;
} 

1020. 潜水员

潜水员为了潜水要使用特殊的装备。

他有一个带2种气体的气缸:一个为氧气,一个为氮气。【价值1,价值2】

让潜水员下潜的深度需要各种数量的氧和氮。

潜水员有一定数量的气缸。

每个气缸都有重量和气体容量。

潜水员为了完成他的工作需要特定数量的氧和氮。

他完成工作所需气缸的总重的最低限度的是多少?

例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:

3 36 120

10 25 129

5 50 250

1 45 130

4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。

你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。

输入格式
第一行有2个整数 m,n。它们表示氧,氮各自需要的量。

第二行为整数 k 表示气缸的个数。

此后的 k 行,每行包括ai,bi,ci,3个整数。这些各自是:第 i 个气缸里的氧和氮的容量及气缸重量。

输出格式
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。

数据范围
1≤m≤21,
1≤n≤79,
1≤k≤1000,
1≤ai≤21,
1≤bi≤79,
1≤ci≤800
输入样例:
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
输出样例:
249

输入样例:
5 60
5
21 1 120
21 50 129
21 5 250
21 2 130
21 2 119
输出样例:
249
简记:f[i][j] = min(f[i][j] , f[i - v1][ j- v2 ] + w); 【 至少是n,m ,可以大于】
求min 可以 res = 0x3f3f3f3f ,memset(f , 0x3f,sizeof f);

#include<bits/stdc++.h>
using namespace std;

const int N = 22 , M = 80;
int n,m,k;
int f[N][M];

int main()
{
	
	cin >> n >> m >> k;
	
	memset(f,0x3f,sizeof f);
	f[0][0] = 0;
	
	while(k --)
	{
		int v1,v2,w;
		cin >> v1 >> v2 >> w;
		for(int i = n; i >= 0; i--)//数组开了N个,从N开始就越界了 
			for(int j = m;j >= 0; j--)
				f[i][j] = min(f[i][j] , f[max(0,i - v1)][max(0,j- v2) ] + w);
	}
	
	int res = 1e9;//接近int_max 【有解,取个较大的数,否则也可以0x3f3f3f3f】
	for(int i = n;i < N;i++)
		for(int j = m;j < M;j ++)
			res = min(res ,f[i][j]);
	
	cout << res << endl; 
	
	return 0;
} 

278. 数字组合

给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。

输入格式
第一行包含两个整数 N 和 M。

第二行包含 N 个整数,表示 A1,A2,…,AN。

输出格式
包含一个整数,表示可选方案数。

数据范围
1≤N≤100,
1≤M≤10000,
1≤Ai≤1000,
答案保证在 int 范围内。

输入样例:
4 4
1 1 2 2
输出样例:
3

思路: 只能选一次,所以子状态必须是没用选过的,所以必须逆序找没有更新过的子状态。

所有只从前个i物品选,且体积恰好是j的方案的集合
分类:包含物品i的所有选法(有就加一个对应价值为vi)
不包含物品i的所有选法(没有vi就已经放满)
f[i,j] = f[i-1,j-vi] + f[i-1,j]

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010;

int n, m;
int f[N];

int main()
{
    cin >> n >> m;//种类,体积
    f[0] = 1;
    for (int i = 0; i < n; i ++ )
    {
        int v;
        cin >> v;
        for (int j = m; j >= v; j -- ) f[j] += f[j - v];
    }

    cout << f[m] << endl;

    return 0;
}

1019. 庆功会

为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。

期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。

输入格式
第一行二个数n,m,其中n代表希望购买的奖品的种数,m表示拨款金额。

接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可)。

输出格式
一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。

数据范围
n≤500,m≤6000,
v≤100,w≤1000,s≤10
输入样例:
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
输出样例:
1040
多重背包模板
简记:数据范围小,不用拆成01 【多重背包模板】
f[j] = max(f[j] , f[j - k * v ] + k * w);


#include <bits/stdc++.h>
using namespace std;

const int  N = 1e4 + 5;
int n,m;
int f[N];

int main()
{
    cin>>n>>m;
    while(n--)
    {
    	int v , w ,s;
        cin>>v>>w>>s;
      	for(int j = m;j >= 0;j --)
		  for(int k = 0;k <= s && k * v <= j;k ++)
		  	f[j] = max(f[j] , f[j - k * v ] + k * w); 	
	}
	
	cout << f[m] << endl;
    
    return 0;
}

思路
多重背包问题:每种物品可用s次
背包的总体积V: 拨款金额m
物品的种数N:奖品的种数n
每种物品的价值w:该奖品的价值
每种物品的体积v:该奖品的价格
状态表示:f[i][j]:只从前i个物品中选,且总体积不超过j的所有选法集合的最大值
状态计算:f[i][j]=max(f[i][j],f[i-1][j-kv]+kw)
在能装下的情况下每种物品可以选0次,1次,2次,,,k次
由于数据范围较小,所以不需要进行二进制,或单调队列优化
时间复杂度 O(nms) 大约为 3e7
这里将二维优化到了一维。

1023. 买书

  1. 问题描述:

小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。问小明有多少种买书方案?(每种书可购买多本)

输入格式

一个整数 n,代表总共钱数。

输出格式

一个整数,代表选择方案种数。

数据范围
0 ≤ n ≤ 1000

输入样例1:
20
输出样例1:
2
输入样例2:
15
输出样例2:
0
输入样例3:
0
输出样例3:
1

完全背包:只从前i个物品选,且总体积恰好是j的方案的集合
属性count (数量)

简记:一维优化 f[j] +=f[j - v[i]]  
#include <bits/stdc++.h>
using namespace std;

const int  N = 1010;

int v[4] = {10,20,50,100}; //书价格 ,下标从0开始 
int f[N];

int main()
{
 	int m; //体积 
 	cin >> m;
	
	f[0] = 1;
	for(int i = 0;i <= 4;i++) //种类 
		for(int j = v[i];j <= m;j++) 
			f[j] += f[j-v[i]];		//一维优化后,恒等式可去 f[j] = f[j];
	
	cout << f[m] << endl;
    
    return 0;
}
朴素:
//朴素 
#include <bits/stdc++.h>
using namespace std;

const int  N = 1010;

int v[] = {0,10,20,50,100}; //书价格 ,i种类下标从1开始 
int f[N][N];

int main()
{
 	int m; //体积 
 	cin >> m;
	
	f[0][0] = 1;
	for(int i = 1;i <= 4;i++) //种类 
		for(int j = 0;j <= m;j++) 
		{
			f[i][j] = f[i-1][j];
			if(j >= v[i]) f[i][j] += f[i][j-v[i]]; 
		}
			//f[j] = max(f[j] , f[j - v[i]] ); 
	
	cout << f[4][m] << endl;
    
    return 0;
}

12. 背包问题求具体方案

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。

物品编号范围是 1…N。

数据范围
0<N,V≤1000
0<vi,wi≤1000

输入样例
4 5
1 2
2 4
3 4
4 6

输出样例:
1 4

字典序最小–>贪心:体积从后往前尽量多选择(多放)字典序(编号)小的
可选可不选(一定要选)
【若从后往前,则后面字典序大的可不选】


#include<bits/stdc++.h>
using namespace std;

const int N = 1010;

int n,m;
int v[N],w[N];
int f[N][N];

int main()
{
	cin >> n >> m;
	
	for(int i = 1;i <= n;i++) cin >> v[i] >> w[i];
	
	for(int i = n;i >= 1;i--)
	{
		for(int j = 0;j <= m;j++)
		{
			f[i][j] = f[i+1][j];
			if(j >= v[i]) f[i][j] = max(f[i][j] ,f[i+1][j - v[i]] + w[i]);
		}
	} 
	
	//f[1][m] 是最大价值 --》字典序最小的最大价值 
	int j = m;
	for(int i = 1;i <= n;i++)
		if(j >= v[i] && f[i][j] == f[i+1][j - v[i]] + w[i])
		{
			cout << i <<" ";
			j -= v[i];
		}
	
	return 0;
}

1013. 机器分配

总公司拥有M台 相同 的高效设备,准备分给下属的N个分公司。

各分公司若获得这些设备,可以为国家提供一定的盈利。盈利与分配的设备数量有关。

问:如何分配这M台设备才能使国家得到的盈利最大?

求出最大盈利值。

分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数M。

输入格式
第一行有两个数,第一个数是分公司数N,第二个数是设备台数M;

接下来是一个N*M的矩阵,矩阵中的第 i 行第 j 列的整数表示第 i 个公司分配 j 台机器时的盈利。

输出格式
第一行输出最大盈利值;

接下N行,每行有2个数,即分公司编号和该分公司获得设备台数。

答案不唯一,输出任意合法方案即可。

数据范围
1≤N≤10,
1≤M≤15

输入样例:
3 3
30 40 50
20 30 50
20 25 30

输出样例:
70
1 1
2 1
3 1

分组背包问题:按公司分组
体积限制台数


#include<bits/stdc++.h>
using namespace std;

const int N = 11,M = 16;

int n,m;//组,体积(总台数)
int w[N][M];//第i家公司,的第k件物品价值 
int f[N][M];//价值
int way[N]; //记录最大价值对应的k

int main()
{
	cin >> n >> m;
	for(int i = 1;i <= n;i++)	
		for(int j = 1;j <= m;j++)
			cin >> w[i][j];
	
	for(int i = 1;i <= n;i++)	
		for(int j = 0;j <= m;j++) //这里合并了f[i][j] = f[i-1][j] ; if(j>=w[i][k])  
			for(int k = 0;k <= j;k ++)
				f[i][j] = max(f[i][j] , f[i - 1][j - k] + w[i][k]);  //f[j] = max(f[j] , f[j - k] + w[k]);  ,但要写出方案(每个公司台数,用二维 
	
	cout << f[n][m] << endl;
	
	int j = m; //选m台时取最大价值 
	for(int i = n ;i >= 0;i--)
		for(int k = 0;k <= j;k++)
			if(f[i][j] == f[i - 1][j - k] +w[i][k] ) //找到转移的来源:k台 
			{
				way[i] = k;
				j -= k; //剩下可分配 
				break;
			}
	//依题意输出 
	for(int i = 1;i <= n ; i++) cout << i << " " << way[i] << endl;
	
	return 0;
}

487. 金明的预算方案

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。

更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。

今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:

在这里插入图片描述

如果要买归类为附件的物品,必须先买该附件所属的主件。

每个主件可以有0个、1个或2个附件。

附件不再有从属于自己的附件。

金明想买的东西很多,肯定会超过妈妈限定的N元。

于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。

他还从因特网上查到了每件物品的价格(都是10元的整数倍)。

他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,…,jk,则所求的总和为:

v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk](其中*为乘号)

请你帮助金明设计一个满足要求的购物单。

输入格式
输入文件的第1行,为两个正整数,用一个空格隔开:N m,其中N表示总钱数,m为希望购买物品的个数。

从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数v p q,其中v表示该物品的价格,p表示该物品的重要度(1~5),q表示该物品是主件还是附件。

如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号。

输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。

数据范围
N<32000,m<60,v<10000
输入样例:
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出样例:
2200

#include<bits/stdc++.h>   //分组背包   主件和附件分成一个组(附件数量分类)   
#include <vector>
using namespace std;

typedef pair<int, int> PII;

const int N = 70, M = 32010;

int n, m; // 中n表示总钱数,m为希望购买物品的个数

PII master[N];// {主件的价值 , 重要度*价值 } 
vector<PII> servant[N];//附件 
int f[M];

int main() {
    cin >> m >> n;
    for (int i = 1; i <= n; i++) {
        int v, w, q;
        cin >> v >> w >> q;
        if (!q) master[i] = {v, v * w};  // q=0,物品为主件
        else servant[q].push_back({v, v * w});  // 否则是附件,价格是v,权重*价值为v * w
    }

	// 0-1背包模板 
    for (int i = 1; i <= n; i++) 
        for (int j = m; j >= 0; j--) {
            auto &sv = servant[i];
            // 枚举所有附件的搭配情况,用状态压缩的办法枚举
            for (int k = 0; k < 1 << sv.size(); k++) {
                int v = master[i].first, w = master[i].second;  //主件体积,价值 
                for (int u = 0; u < sv.size(); u++)
                    if (k >> u & 1) //二进制优化转换为01背包 
					{
                        v += sv[u].first;
                        w += sv[u].second;
                    }
                    
                if (j >= v) f[j] = max(f[j], f[j - v] + w);
            }
        }

    cout << f[m] << endl;

    return 0;
}

426. 开心的金明

题目描述
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。

更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。

今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N元。

于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。

他还从因特网上查到了每件物品的价格(都是整数元)。

他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,…,jk,则所求的总和为:

v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk]
请你帮助金明设计一个满足要求的购物单。

输入格式
输入文件的第1行,为两个正整数N和m,用一个空格隔开。(其中N表示总钱数,m为希望购买物品的个数)

从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有2个非负整数v和p。(其中v表示该物品的价格,p表示该物品的重要度)

输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(数据保证结果不超过100000000)。

数据范围
1≤N<30000,
1≤m<25,
0≤v≤10000,
1≤p≤5

输入样例:

1000 5
800 2
400 5
300 5
400 3
200 2

输出样例:

3900

#include<bits/stdc++.h>
using namespace std;

const int N = 30010, M = 30;

int n, m;//n种类,体积(最高可承受预算) 
int v, w;//v价值、w权重
int f[N];//最大价值 

int main()
{
    cin >> m >> n;
    for(int i = 1; i <= n; i ++) //从1开始 
	{
        cin >> v >> w;
        for(int j = m; j >= v; j --) 
                f[j] = max(f[j], f[j - v] + w * v); //加对应权重值 w*v 
    }

    cout << f[m] << endl;

    return 0;
}

1021. 货币系统

给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。

输入格式
第一行,包含两个整数n和m。

接下来n行,每行包含一个整数,表示一种货币的面值。

输出格式
共一行,包含一个整数,表示方案数。

数据范围
n≤15,m≤3000
输入样例:
3 10
1
2
5
输出样例:
10

(与1023.买书那叫一个一模一样)

简记:f[i,j] = max(f[i-1,j] , f[i-1,j-vi])  选i/不选i 到达j体积包含前i个物品
	【完全背包】一维优化:for(int j = a;j <= m;j++) f[j] += f[j - a];
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 3010;

int n,m;
int f[N];

int main()
{
	cin >> n>> m;
	
	f[0] = 1;  //不选也是1种方案 
	for(int i = 0;i < n;i++)
	{
		int a;
		cin >> a;
		for(int j = a;j <= m;j++) f[j] += f[j - a];
		
	}
	
	cout << f[m] << endl;
		
	return 0;
}

532. 货币系统

在网友的国度中共有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。

为了方便,我们把货币种数为 n、面额数组为 a[1…n] 的货币系统记作 (n,a)。

在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i]×t[i] 的和为 x。

然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 x 不能被该货币系统表示出。

例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。

两个货币系统 (n,a) 和 (m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

现在网友们打算简化一下货币系统。

他们希望找到一个货币系统 (m,b),满足 (m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。

他们希望你来协助完成这个艰巨的任务:找到最小的 m。

输入格式
输入文件的第一行包含一个整数 T,表示数据的组数。

接下来按照如下格式分别给出 T 组数据。

每组数据的第一行包含一个正整数 n。

接下来一行包含 n 个由空格隔开的正整数 a[i]。

输出格式
输出文件共有 T 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。

数据范围
1≤n≤100,
1≤a[i]≤25000,
1≤T≤20
输入样例:

2
4
3 19 10 6
5
11 29 13 19 17

输出样例:

2
5

性质1:a1 ,a2 ,…,an 一定可以被表示出来
性质2:在最优解中,b1,b2,…,bm一定都是从a1,a2,…,an中选择的
性质3: b1,b2…,bm一定不能被其他bi 表示出来

typedef long long ll;

const int N = 110,M = 25010;

int n;
int a[N]; 
int f[M];

int main()
{
	int T;
	cin >> T;
	
	while(T--)
	{
		cin >> n;
		for(int i = 0;i < n;i++) cin >> a[i];
		
		sort(a,a + n);
		
		int m = a[n - 1]; //遍历到最大的数 
		memset(f , 0 , sizeof f);
		f[0] = 1; 
		
		int res = 0;
		
		for(int i = 0;i < n;i++)
		{
			if(!f[a[i]]) res ++;
			for(int j = a[i]; j <= m;j ++) //m = a[n - 1]
				f[j] += f[j - a[i]];
		 } 
				
		cout << res << endl;
	}
		
	return 0;
}


7. 混合背包问题

有 N 种物品和一个容量是 V 的背包。

物品一共有三类:

第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8

01/多重/完全 【均为:只从前i件物品中选,且总体积不超过j的选法】

10. 有依赖的背包问题

有 N 个物品和一个容量是 V 的背包。物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。如下图所示:
在这里插入图片描述

如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。输出最大价值。

输入格式

第一行有两个整数 N,V,用空格隔开,分别表示物品个数和背包容量。接下来有 N 行数据,每行数据表示一个物品。第 i 行有三个整数 vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。如果 pi = −1,表示根节点。 数据保证所有物品构成一棵树。

输出格式

输出一个整数,表示最大价值。

数据范围

1 ≤ N,V ≤ 100,1 ≤ vi,wi ≤ 100
父节点编号范围:内部结点:1 ≤ pi ≤ N;根节点 pi = −1;

输入样例

5 7
2 3 -1
2 2 1
3 5 1
4 7 2
3 6 2

输出样例:

11

11. 背包问题求方案数

  1. 问题描述:

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最优选法的方案数。注意答案可能很大,请输出答案模10 ^ 9 + 7的结果。

输入格式

第一行两个整数N,V,用空格隔开,分别表示物品数量和背包容积。接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

输出一个整数,表示 方案数模10 ^ 9 + 7 的结果。

数据范围

0 < N,V ≤ 1000
0 < vi,wi ≤ 1000

输入样例

4 5
1 2
2 4
3 4
4 6

输出样例:

2

734. 能量石

  1. 问题描述:

岩石怪物杜达生活在魔法森林中,他在午餐时收集了 N 块能量石准备开吃。由于他的嘴很小,所以一次只能吃一块能量石。能量石很硬,吃完需要花不少时间。吃完第 i 块能量石需要花费的时间为 Si 秒。杜达靠吃能量石来获取能量。不同的能量石包含的能量可能不同。此外,能量石会随着时间流逝逐渐失去能量。第 i 块能量石最初包含 Ei 单位的能量,并且每秒将失去 Li 单位的能量。当杜达开始吃一块能量石时,他就会立即获得该能量石所含的全部能量(无论实际吃完该石头需要多少时间)。能量石中包含的能量最多降低至 0。请问杜达通过吃能量石可以获得的最大能量是多少?

输入格式

第一行包含整数 T,表示共有 T 组测试数据。每组数据第一行包含整数 N,表示能量石的数量。接下来 N 行,每行包含三个整数 Si,Ei,Li。

输出格式

每组数据输出一个结果,每个结果占一行。结果表示为 Case #x: y,其中 x 是组别编号(从 1 开始),y 是可以获得的最大能量值。

数据范围

1 ≤ T ≤ 10,
1 ≤ N ≤ 100,
1 ≤ Si ≤ 100,
1 ≤ Ei ≤ 10 ^ 5,
0 ≤ Li ≤ 10 ^ 5

输入样例:

3
4
20 10 1
5 30 5
100 30 1
5 80 60
3
10 4 1000
10 3 1000
10 8 1000
2
12 300 50
5 200 0

输出样例:

Case #1: 105
Case #2: 8
Case #3: 500
样例解释

在样例#1中,有 N=4 个宝石。杜达可以选择的一个吃石头顺序是:
吃第四块石头。这需要 5 秒,并给他 80 单位的能量。
吃第二块石头。这需要 5 秒,并给他 5 单位的能量(第二块石头开始时具有 30 单位能量,5 秒后失去了 25 单位的能量)。
吃第三块石头。这需要 100 秒,并给他 20 单位的能量(第三块石头开始时具有 30 单位能量,10 秒后失去了 10 单位的能量)。
吃第一块石头。这需要 20 秒,并给他 0 单位的能量(第一块石头以 10 单位能量开始,110 秒后已经失去了所有的能量)。
他一共获得了 105 单位的能量,这是能获得的最大值,所以答案是 105。

在样本案例#2中,有 N=3 个宝石。
无论杜达选择吃哪块石头,剩下的两个石头的能量都会耗光。所以他应该吃第三块石头,给他提供 8 单位的能量。
在样本案例#3中,有 N=2 个宝石。杜达可以:
吃第一块石头。这需要 12 秒,并给他 300 单位的能量。
吃第二块石头。这需要 5 秒,并给他 200 单位的能量(第二块石头随着时间的推移不会失去任何能量!)。
所以答案是 500。


第二章 搜索

DFS之连通性模型

1112. 迷宫

一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由 n∗n
的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不能通行。

同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上,Extense想要从点A走到点B,问在不走出迷宫的情况下能不能办到。

如果起点或者终点有一个不能通行(为#),则看成无法办到。

注意:A、B不一定是两个不同的点。

输入格式
第1行是测试数据的组数 k,后面跟着 k 组输入。
每组测试数据的第1行是一个正整数 n,表示迷宫的规模是 n∗n 的。

接下来是一个 n∗n 的矩阵,矩阵中的元素为.或者#。

再接下来一行是 4 个整数 ha,la,hb,lb,描述 A 处在第 ha 行, 第 la 列,B 处在第 hb 行, 第 lb 列。

注意到 ha,la,hb,lb 全部是从 0 开始计数的。

输出格式k行,每行输出对应一个输入。

能办到则输出“YES”,否则输出“NO”。

数据范围
1≤n≤100
输入样例:
2
3
.##
…#
#…
0 0 2 2
5

###.#
…#…
###…
…#.
0 0 4 0
输出样例:
YES
NO


(迷宫之DFS【好处代码短-ACM按时间排名】)

DFS与BFS比较的不足之处:
BFS除了能知道是否能走到终点(start–>end), 还可以求出最短距离(步数) min_dist(start–>end)    且距离数组 d 可用于标记 ~~~且距离数组d可用于标记    且距离数组d可用于标记

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

int n;
char g[N][N];
int xa, ya, xb, yb;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
bool st[N][N];

bool dfs(int x, int y)
{
    if (g[x][y] == '#') return false; //移到前面判断
    if (x == xb && y == yb) return true;

    st[x][y] = true;

    for (int i = 0; i < 4; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= n || b < 0 || b >= n) continue; //越界
        if (st[a][b]) continue; //标记
        if (dfs(a, b)) return true; //最终能走到则为true
    }

    return false; //最终没有返回true(判断找到终点提前终止), 则最后为false
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        scanf("%d", &n);
        for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);
        scanf("%d%d%d%d", &xa, &ya, &xb, &yb);

        memset(st, 0, sizeof st);
        if (dfs(xa, ya)) puts("YES");
        else puts("NO");
    }

    return 0;
}

BFS版

#include <bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
const int N = 210;

int n, m;
int sx, sy, ex, ey; //每轮给定 起点、终点     
char g[N][N];
PII q[N * N];
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};

void bfs(int x, int y)
{
    int hh = 0, tt = -1;
    q[++ tt] = {x, y};

    while(hh <= tt)
    {
        auto t = q[hh++];

        for(int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            if(a >= 0 && a < n && b >= 0 && b < n && g[a][b] != '#') 
            {
                if(a == ex && b == ey) 
                {
                    puts("YES");
                    return;
                }
                q[++tt] = {a, b};
                g[a][b] = '#';   
            }
        }
    }
    puts("NO");
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        scanf("%d%d", &n);
        for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);//按字符串读入每行
           
        scanf("%d%d%d%d", &sx, &sy, &ex, &ey);
        if(g[sx][sy] == '#' || g[ex][ey] == '#') puts("NO");
        else bfs(sx, sy); 
       
    }

    return 0;
}

1113. 红与黑

DFS-连通块元素个数统计【Flood Fill】

DFS是否需要回溯(以棋盘举例):
整个棋盘看做一个整体能否从一种棋盘状态转移到另一种棋盘状态 【需要回溯】
棋盘内每个元素为整体, 改变元素转移到一种棋盘状态 【不用回溯】
(可以简单记为: 遍历所有可能性【需要回溯】)

2023-bfs

#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 25;

int n, m;
char g[N][N];
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
PII q[N * N]; //【最多遍历所有点N * N】

int bfs(int x, int y)
{
    int cnt = 1;
    int hh = 0, tt = -1;
    q[++tt] = {x, y};
    while(hh <= tt)
    {
        auto t = q[hh++];    
        for(int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            if(a >= 0 && a < n && b >= 0 && b < m && g[a][b] == '.') 
            {
                g[a][b] = '#';  //【直接修改为障碍物-等效标记走过-不会重复统计】
                q[++tt] = {a, b};
                cnt ++;
            }
        }
    }
    return cnt;
}

int main()
{
    while (cin >> m >> n, n || m)
    {
        for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);  //读入一行

        int x, y;
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ )
                if (g[i][j] == '@')//找到起点
                {
                    x = i;
                    y = j;
                }

        printf("%d\n", bfs(x, y));
    }

    return 0;
}
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 25;

int n, m;
char g[N][N];
bool st[N][N];

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int dfs(int x, int y)//起点开始
{   //递归理解:【递归到最后一层每个元素cnt = 1, 返回时累加】
    int cnt = 1;  //计算每个连通块元素个数:起点算1个 

    st[x][y] = true;
    for (int i = 0; i < 4; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= n || b < 0 || b >= m) continue;//合并: if(越界 || 不是黑色 || 标记走过) continue; 判断下一个  
        if (g[a][b] != '.') continue;
        if (st[a][b]) continue;
    
        cnt += dfs(a, b);//能到的点的数量 【递归返回累加】
    }

    return cnt;
}

int main()
{   //此题坑点!!!【先列m后行n】
    while (cin >> m >> n, n || m) //所有表达式都会执行,只不过返回值是最后一个表达式的值
    {
        for (int i = 0; i < n; i ++ ) cin >> g[i];

        int x, y;
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ )
                if (g[i][j] == '@')//起点
                {
                    x = i;
                    y = j;
                }

        memset(st, 0, sizeof st);//多组数据,每次要把标记数组清空一遍
        cout << dfs(x, y) << endl;
    }

    return 0;
}

节省标记st法 + ne二维方向向量

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 30;

int n, m;

int cnt;

int ne[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};

char g[N][N];

void dfs(int x, int y)
{
    for (int i = 0; i < 4; i ++ )
    {
        int tx = x + ne[i][0], ty = y + ne[i][1];
        if (tx < 0 || tx >= n || ty < 0 || ty >= m || g[tx][ty] == '#') continue;
        g[tx][ty] = '#';  //节省st标记法【走过改为障碍】
        cnt ++;
        dfs(tx, ty); 
    }
}

int main()
{
    while (cin >> m >> n, n || m)
    {
        for (int i = 0; i < n; i ++ ) cin >> g[i];
            
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++)
                if (g[i][j] == '@')
                {
                    g[i][j] = '#'; //节省st标记法【走过改为障碍】
                    cnt = 1;
                    dfs(i, j);
                    break;
                }
        printf("%d\n", cnt);
    }
    return 0;
}

【待更新】


状态机模型
## 1049. 大盗阿福
## 1057. 股票买卖 IV
## 1058. 股票买卖 V
## 1052. 设计密码
## 1053. 修复DNA

状态压缩DP
## 1064. 小国王
## 327. 玉米田
## 292. 炮兵阵地
## 524. 愤怒的小鸟
## 529. 宝藏

区间DP
## 1068. 环形石子合并
## 320. 能量项链
## 479. 加分二叉树
## 1069. 凸多边形的划分
## 321. 棋盘分割

树形DP
## 1072. 树的最长路径
## 1073. 树的中心
## 1075. 数字转换
## 1074. 二叉苹果树
## 323. 战略游戏
## 1077. 皇宫看守

数位DP
## 1081. 度的数量
## 1082. 数字游戏
## 1083. Windy数
## 1084. 数字游戏 II
## 1085. 不要62
## 1086.7不成妻

单调队列优化DP
## 135. 最大子序和
## 1087. 修剪草坪
## 1088. 旅行问题
## 1089. 烽火传递
## 1090. 绿色通道
## 1091. 理想的正方形

斜率优化DP
## 300. 任务安排1
## 301. 任务安排2
## 302. 任务安排3
## 303. 运输小猫

第二章 搜索 
包括Flood Fill、最短路模型、多源BFS、最小步数模型、双端队列广搜、双向广搜、A*、DFS之连通性模型、DFS之搜索顺序、DFS之剪枝与优化、迭代加深、双向DFS、IDA*等内容

Flood Fill
## 1097. 池塘计数
## 1098. 城堡问题
## 1106. 山峰和山谷

最短路模型
## 1076. 迷宫问题
## 188. 武士风度的牛
## 1100. 抓住那头牛

多源BFS
## 173. 矩阵距离

最小步数模型
## 1107. 魔板

双端队列广搜
## 175. 电路维修

双向广搜
## 190. 字串变换

A*
## 178. 第K短路
## 179. 八数码

DFS之连通性模型
## 1112. 迷宫
## 1113. 红与黑

DFS之搜索顺序
## 1116. 马走日
## 1117. 单词接龙
## 1118. 分成互质组

DFS之剪枝与优化
## 165. 小猫爬山
## 166. 数独
## 167. 木棒
## 168. 生日蛋糕

迭代加深
## 170. 加成序列

双向DFS
## 171. 送礼物

IDA*
## 180. 排书
## 181. 回转游戏

第三章 图论 
包括单源最短路的建图方式、单源最短路的综合应用、单源最短路的扩展应用、Floyd算法、最小生成树、最小生成树的扩展应用、负环、差分约束、最近公共祖先、强连通分量、双连通分量、二分图、欧拉回路和欧拉路径、拓扑排序等内容

单源最短路的建图方式
## 1129. 热浪
## 1128. 信使
## 1127. 香甜的黄油
## 1126. 最小花费
## 920. 最优乘车
## 903. 昂贵的聘礼

单源最短路的综合应用
## 1135. 新年好
## 340. 通信线路
## 342. 道路与航线
## 341. 最优贸易

单源最短路的扩展应用
## 1137. 选择最佳线路
## 1131. 拯救大兵瑞恩
## 1134. 最短路计数
## 383. 观光

Floyd算法
## 1125. 牛的旅行
## 343. 排序
## 344. 观光之旅
## 345. 牛站

最小生成树
## 1140. 最短网络
## 1141. 局域网
## 1142. 繁忙的都市
## 1143. 联络员
## 1144. 连接格点

最小生成树的扩展应用
## 1146. 新的开始
## 1145. 北极通讯网络
## 346. 走廊泼水节
## 1148. 秘密的牛奶运输

负环
## 904. 虫洞
## 361. 观光奶牛
## 1165. 单词环

差分约束
## 1169. 糖果
## 362. 区间
## 1170. 排队布局
## 393. 雇佣收银员

最近公共祖先
## 1172. 祖孙询问
## 1171. 距离
## 356. 次小生成树
## 352. 闇の連鎖

有向图的强连通分量
## 1174. 受欢迎的牛
## 367. 学校网络
## 1175. 最大半连通子图
## 368. 银河

无向图的双连通分量
## 395. 冗余路径
## 1183. 电力
## 396. 矿场搭建

二分图
## 257. 关押罪犯
## 372. 棋盘覆盖
## 376. 机器任务
## 378. 骑士放置
## 379. 捉迷藏

欧拉回路和欧拉路径
## 1123. 铲雪车
## 1184. 欧拉回路
## 1124. 骑马修栅栏
## 1185. 单词游戏

拓扑排序
## 1191. 家谱树
## 1192. 奖金
## 164. 可达性统计
## 456. 车站分级

第四章 高级数据结构 
包括并查集、树状数组、线段树、可持久化数据结构、平衡树、AC自动机等内容

并查集
## 1250. 格子游戏
## 1252. 搭配购买
## 237. 程序自动分析
## 239. 奇偶游戏
## 238. 银河英雄传说

树状数组
## 241. 楼兰图腾
## 242. 一个简单的整数问题
## 243. 一个简单的整数问题2
## 244. 谜一样的牛

线段树
## 1275. 最大数
## 245. 你能回答这些问题吗
## 246. 区间最大公约数
## 243. 一个简单的整数问题2
## 247. 亚特兰蒂斯
## 1277. 维护序列

可持久化数据结构
## 256. 最大异或和
## 255. 第K小数

平衡树
## 253. 普通平衡树
## 265. 营业额统计

AC自动机
## 1282. 搜索关键词
## 1285. 单词

第五章 数学知识 
包括筛质数、分解质因数、快速幂、约数个数、欧拉函数、同余、矩阵乘法、组合计数、高斯消元、容斥原理、概率与数学期望、博弈论等内容

筛质数
## 1292. 哥德巴赫猜想
## 1293. 夏洛克和他的女朋友
## 196. 质数距离

分解质因数
## 197. 阶乘分解

快速幂
## 1289. 序列的第k个数
## 1290. 越狱

约数个数
## 1291. 轻拍牛头
## 1294. 樱花
## 198. 反素数
## 200. Hankson的趣味题

欧拉函数
## 201. 可见的点
## 220. 最大公约数

同余
## 203. 同余方程
## 222. 青蛙的约会
## 202. 最幸运的数字
## 1298. 曹冲养猪

矩阵乘法
## 1303. 斐波那契前 n 项和
## 1304. 佳佳的斐波那契
## 1305. GT考试

组合计数
## 1307. 牡牛和牝牛
## 1308. 方程的解
## 1309. 车的放置
## 1310. 数三角形
## 1312. 序列统计
## 1315. 网格
## 1316. 有趣的数列

高斯消元
## 207. 球形空间产生器
## 208. 开关问题

容斥原理
## 214. Devu和鲜花
## 215. 破译密码

概率与数学期望
## 217. 绿豆蛙的归宿
## 218. 扑克牌

博弈论
## 1319. 移棋子游戏
## 1321. 取石子
## 1322. 取石子游戏

第六章 基础算法 
包括位运算、递推与递归、前缀和与差分、二分、排序、RMQ等内容

位运算
## 90. 64位整数乘法

递推与递归
## 95. 费解的开关
## 97. 约数之和
## 98. 分形之城

前缀和与差分
## 99. 激光炸弹
## 100. 增减序列

二分
## 102. 最佳牛围栏
## 113. 特殊排序

排序
## 105. 七夕祭
## 106. 动态中位数
## 107. 超快速排序

RMQ
## 1273. 天才的记忆

  • 34
    点赞
  • 236
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 28
    评论
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

violet~evergarden

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

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

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

打赏作者

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

抵扣说明:

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

余额充值