#include
#define ll long long
using namespace std;
int n,ans;
int a[10005],f[10005];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[j]<a[i]) f[i]=max(f[i],f[j]);
}
f[i]++;
ans=max(ans,f[i]);
}
cout<<ans<<endl;
return 0;
}
我们肯定要知道我们当前阶段最后一个元素为多少,还有当前我们的序列有多长。
前两种方法都是用前者做状态,我们为什么不可以用后做状态呢?
设 F[i]表示长度为 i 的最长上升子序列的末尾元素的最小值,我们发现这个数组的权值一定单调不降(仔细想一想,这就是我们贪心的来由)。于是我们按顺序枚举数组 A ,每一次对 F 数组二分查找,找到小于 A[i] 的最大的 F[j] ,并用它将 F[j+1]更新。
#include
#define ll long long
using namespace std;
const int N=200005;
int n;
int a[N];
int f[N];
int main(){
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
int ans=1; f[1]=a[1];
for(int i=2;i<=n;++i){
int l=1,r=ans,mid;
while(l<=r){
mid=(l+r)>>1;
if(a[i]<=f[mid])r=mid-1;
else l=mid+1;
}f[l]=a[i];
if(l>ans)++ans;
}cout<<ans<<endl;
return 0;
}
LCS问题具有最优子结构
两个序列的LCS问题包含两个序列的前缀的LCS,因此,LCS问题具有最优子结构性质。在设计递归算法时,不难看出递归算法具有子问题重叠的性质。
设C[i,j]表示Xi和Yj的最长公共子序列LCS的长度。如果i=0或j=0,即一个序列长度为0时,那么LCS的长度为0。根据LCS问题的最优子结构性质,可得如下公式:
#include
#include
#define MAXLEN 50
void LCSLength(char *x, char *y, int m, int n, int c[][MAXLEN], int b[][MAXLEN])
{
int i, j;
for(i = 0; i <= m; i++)
c[i][0] = 0;
for(j = 1; j <= n; j++)
c[0][j] = 0;
for(i = 1; i<= m; i++)
{
for(j = 1; j <= n; j++)
{
if(x[i-1] == y[j-1])
{
c[i][j] = c[i-1][j-1] + 1;
b[i][j] = 1;
}
else if(c[i-1][j] >= c[i][j-1])
{
c[i][j] = c[i-1][j];
b[i][j] = 3;
}
else
{
c[i][j] = c[i][j-1];
b[i][j] = 2;
}
}
}
}
void PrintLCS(int b[][MAXLEN], char *x, int i, int j)
{
if(i == 0 || j == 0)
return;
if(b[i][j] == 1)
{
PrintLCS(b, x, i-1, j-1);
printf("%c ", x[i-1]);
}
else if(b[i][j] == 3)
PrintLCS(b, x, i-1, j);
else
PrintLCS(b, x, i, j-1);
}
int main()
{
char x[MAXLEN] = {"ABCBDAB"};
char y[MAXLEN] = {"BDCABA"};
int b[MAXLEN][MAXLEN];
int c[MAXLEN][MAXLEN];
int m, n;
m = strlen(x);
n = strlen(y);
LCSLength(x, y, m, n, c, b);
PrintLCS(b, x, m, n);
return 0;
}
#include
using namespace std;
const int N = 3010;
int n;
int a[N], b[N];
int f[N][N];
int main()
{
cin>>n;
for (int i = 1; i <= n; i ++ ) cin>>a[i];
for (int i = 1; i <= n; i ++ ) cin>>b[i];
for (int i = 1; i <= n; i ++ )
{
int maxv = 1;
for (int j = 1; j <= n; j ++ )
{
f[i][j] = f[i - 1][j];//集合左半部分
if (a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
if (a[i] > b[j]) maxv = max(maxv, f[i - 1][j] + 1);//集合右半部分
}
}
int res = 0;
for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);//最长的情况会出现在以B【n】结尾的状况中,取其中的最大值
cout<<res;
return 0;
}
核心性质:
从高到低依次安排每个同学的位置,那么在安排过程中,当前同学一定占据每排最靠左的连续若干个位置,且从后往前每排人数单调递减。否则一定不满足“每排从左到右身高递减,从后到前身高也递减”这个要求。
DP的核心思想是用集合来表示一类方案,然后从集合的维度来考虑状态之间的递推关系。
受上述性质启发,状态表示为:
f[a][b][c][d][e]代表从后往前每排人数分别为a, b, c, d, e的所有方案的集合, 其中a >= b >= c >= d >= e;
f[a][b][c][d][e]的值是该集合中元素的数量;
状态计算对应集合的划分,令最后一个同学被安排在哪一排作为划分依据,可以将f[a][b][c][d][e]划分成5个不重不漏的子集:
当a > 0 && a - 1 >= b时,最后一个同学可能被安排在第1排,方案数是f[a - 1][b][c][d][e];
当b > 0 && b - 1 >= c时,最后一个同学可能被安排在第2排,方案数是f[a][b - 1][c][d][e];
当c > 0 && c - 1 >= d时,最后一个同学可能被安排在第3排,方案数是f[a][b][c - 1][d][e];
当d > 0 && d - 1 >= e时,最后一个同学可能被安排在第4排,方案数是f[a][b][c][d - 1][e];
当e > 0时,最后一个同学可能被安排在第5排,方案数是f[a][b][c][d][e - 1];
#include
#include
#include
using namespace std;
typedef long long LL;
const int N = 31;
int n;
LL f[N][N][N][N][N];
int main()
{
while (cin >> n, n)
{
int s[5] = {0};
for (int i = 0; i < n; i ++ ) cin >> s[i];
memset(f, 0, sizeof f);
f[0][0][0][0][0] = 1;
for (int a = 0; a <= s[0]; a ++ )
for (int b = 0; b <= min(a, s[1]); b ++ )
for (int c = 0; c <= min(b, s[2]); c ++ )
for (int d = 0; d <= min(c, s[3]); d ++ )
for (int e = 0; e <= min(d, s[4]); e ++ )
{
LL &x = f[a][b][c][d][e];
if (a && a - 1 >= b) x += f[a - 1][b][c][d][e];//映射,将每个这样的同学去掉
if (b && b - 1 >= c) x += f[a][b - 1][c][d][e];
if (c && c - 1 >= d) x += f[a][b][c - 1][d][e];
if (d && d - 1 >= e) x += f[a][b][c][d - 1][e];
if (e) x += f[a][b][c][d][e - 1];
}
cout << f[s[0]][s[1]][s[2]][s[3]][s[4]] << endl;
}
return 0;
}
解法一:无优化法
#include
using namespace std;
const int N=55;
int n,m;
int w[N][N];
int f[N][N][N][N];
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 x1=1;x1<=n;x1++)
for(int y1=1;y1<=m;y1++)
for(int x2=1;x2<=n;x2++)
for(int y2=1;y2<=m;y2++){
int t=w[x1][y1];
if(x1!=x2) t+=w[x2][y2];
f[x1][y1][x2][y2]=max(f[x1][y1][x2][y2],f[x1-1][y1][x2-1][y2]+t);
f[x1][y1][x2][y2]=max(f[x1][y1][x2][y2],f[x1-1][y1][x2][y2-1]+t);
f[x1][y1][x2][y2]=max(f[x1][y1][x2][y2],f[x1][y1-1][x2-1][y2]+t);
f[x1][y1][x2][y2]=max(f[x1][y1][x2][y2],f[x1][y1-1][x2][y2-1]+t);
}
cout<<f[n][m][n][m];
return 0;
}
解法二:解法一的优化
首先考虑路径有交集该如何处理。
可以发现交集中的格子一定在每条路径的相同步数处。因此可以让两个人同时从起点出发,每次同时走一步,这样路径中相交的格子一定在同一步内。状态表示:f[k, i, j] 表示两个人同时走了k步,第一个人在 (i, k - i) 处,第二个人在 (j, k - j)处的所有走法的最大分值。
状态计算:按照最后一步两个人的走法分成四种情况:
两个人同时向右走,最大分值是 f[k - 1, i, j] + score(k, i, j); 第一个人向右走,第二个人向下走,最大分值是f[k - 1, i, j - 1] + score(k, i, j); 第一个人向下走,第二个人向右走,最大分值是 f[k - 1, i- 1, j] + score(k, i, j); 两个人同时向下走,最大分值是 f[k - 1, i - 1, j - 1] + score(k, i, j); 注意两个人不能走到相同格子,即i和j不能相等。
#include
using namespace std;
const int N=55;
int n,m;
int w[N][N];
int f[N*2][N][N];
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 k=1;k<=n+m;k++)
for(int x1=max(1,k-m);x1<=min(k-1,n);x1++)
for(int x2=max(1,k-m);x2<=min(k-1,n);x2++){
int t=w[x1][k-x1];
if(x1!=x2) t+=w[x2][k-x2];
for(int a=0;a<=1;a++)
for(int b=0;b<=1;b++){
f[k][x1][x2]=max(f[k][x1][x2],f[k-1][x1-a][x2-b]+t);
}
}
cout<<f[n+m][n][n];
return 0;
}
采用倒序状态时,对应每个物品唯一且只能使用一次
#include
using namespace std;
const int N=10010;
int f[N],v[N],w[N],ans;
int n,m;
int main(){
cin>>n>>m;
memset(f,0xcf,sizeof f); //0xcf = -INF
f[0]=0;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++){
//采用倒序状态时,对应每个物品唯一且只能使用一次
for(int j=m;j>=v[i];j--) f[j]=max(f[j], f[j-v[i]] + w[i]);
}
for(int j=0;j<=m;j++) ans=max(ans,f[j]);
cout<<ans<<endl;
return 0;
}
给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。
#include
using namespace std;
const int N=10010;
int f[N];
int n,m;
int main(){
cin>>n>>m;
f[0]=1;
while(n--){
int v;
cin>>v;
for(int i=m;i>=v;i--) f[i]+=f[i-v];
}
cout<<f[m]<<endl;
return 0;
}
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。
为此,他想拜附近最有威望的医师为师。
医师为了判断他的资质,给他出了一个难题。
医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入格式
输入文件的第一行有两个整数T和M,用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。
接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出文件包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
数据范围
1≤T≤1000
1≤T≤1000
,
1≤M≤100
1≤M≤100
输入样例:
70 3
71 100
69 1
1 2
输出样例:
3
采药总时间相当于背包容量,每一株药相当于一件物品,采药时间相当于该物品的体积,草药的价值相当于物品价值。
时间复杂度
01背包问题的时间复杂度等于 物品数量 × 背包容量,因此本题的时间复杂度是 O(nm)O(nm)。
#include
using namespace std;
const int N=1010;
int n,t;
int f[N];
int main()
{
cin>>t>>m;
for(int i=0;i<m;i++)
{
int v,w;
cin>>v>>w;
for(int j=t;j>=v;j--)
{
f[j]=max(f[j],f[j-v]+w);
}
}
cout<<f[t]<<endl;
}
有一个箱子容量为 V,同时有 n 个物品,每个物品有一个体积(正整数)。
要求 n 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
输入格式
第一行是一个整数 V,表示箱子容量。
第二行是一个整数 n,表示物品数。
接下来 n 行,每行一个正整数(不超过10000),分别表示这 n 个物品的各自体积。
输出格式
一个整数,表示箱子剩余空间。
数据范围
0
0
24
6
8
3
12
7
9
7
输出样例:
0
最小剩余体积等于体积减去01背包所求能装的最大体积
此题的物品价值和所耗费体力均为物品的体积
#include
using namespace std;
const int N=20010;
int n,v;
int f[N];
int main()
{
cin>>v>>n;
for(int i=0;i<n;i++)
{
int w;
cin>>w;
for(int j=v;j>=w;j--)
{
f[j]=max(f[j],f[j-w]+w);
}
}
cout<<v-f[v]<<endl;
}
宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。 输入样例1: 输出样例1: 输入样例2: 输出样例2: 采用正序状态时对应每个物品可以使用无限次 给定一个自然数 N,要求把 N 拆分成若干个正整数相加的形式,参与加法运算的数可以重复。 注意: 拆分方案不考虑顺序; 设有 N 堆石子排成一排,其编号为 1,2,3,…,N。 每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。 每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。 例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1,2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24; 如果第二步是先合并 2,3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。 问题是:找出一种合理的方法,使总的代价最小,输出最小代价。 输入格式 第二行 N 个数,表示每堆石子的质量(均不超过 1000)。 输出格式 数据范围 “多边形游戏”是一款单人益智游戏。 游戏开始时,给定玩家一个具有 N 个顶点 N 条边(编号 1∼N)的多边形,如图 1 所示,其中 N=4。 每个顶点上写有一个整数,每个边上标有一个运算符 +(加号)或运算符 *(乘号)。 第一步,玩家选择一条边,将它删除。 接下来在进行 N−1 步,在每一步中,玩家选择一条边,把这条边以及该边连接的两个顶点用一个新的顶点代替,新顶点上的整数值等于删去的两个顶点上的数按照删去的边上标有的符号进行计算得到的结果。 下面是用图 1 给出的四边形进行游戏的全过程。 最终,游戏仅剩一个顶点,顶点上的数值就是玩家的得分,上图玩家得分为 0。 请计算对于给定的 N 边形,玩家最高能获得多少分,以及第一步有哪些策略可以使玩家获得最高得分。 输入格式 第二行用来描述多边形所有边上的符号以及所有顶点上的整数,从编号为 1 的边开始,边、点、边…按顺序描述。 其中描述顶点即为输入顶点上的整数,描述边即为输入边上的符号,其中加号用 t 表示,乘号用 x 表示。 输出格式 在第二行,将满足得到最高分数的情况下,所有的可以在第一步删除的边的编号从小到大输出,数据之间用空格隔开。 数据范围 Ural 大学有 N 名职员,编号为 1∼N。 他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。 每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。 现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。 在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。 输入格式 接下来 N 行,第 i 行表示 i 号职员的快乐指数 Hi。 接下来 N−1 行,每行输入一对整数 L,K,表示 K 是 L 的直接上司。 输出格式 数据范围 树形分组背包 学校实行学分制。 每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。 学校开设了 N 门的选修课程,每个学生可选课程的数量 M 是给定的。 学生选修了这 M 门课并考核通过就能获得相应的学分。 在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其他的一些课程的基础上才能选修。 例如《Windows程序设计》必须在选修了《Windows操作基础》之后才能选修。 我们称《Windows操作基础》是《Windows程序设计》的先修课。 每门课的直接先修课最多只有一门。 两门课可能存在相同的先修课。 你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修条件。 假定课程之间不存在时间上的冲突。 输入格式 接下来 N 行每行代表一门课,课号依次为 1,2,…,N。 每行有两个数(用一个空格隔开),第一个数为这门课先修课的课号(若不存在先修课则该项为 0),第二个数为这门课的学分。 学分是不超过 10 的正整数。 输出格式 法一:分情况讨论 在某个星球上,一天由 N 个小时构成,我们称 0 点到 1 点为第 1 个小时、1 点到 2 点为第 2 个小时,以此类推。 在第 i 个小时睡觉能够恢复 Ui 点体力。 在这个星球上住着一头牛,它每天要休息 B 个小时。 它休息的这 B 个小时不一定连续,可以分成若干段,但是在每段的第一个小时,它需要从清醒逐渐入睡,不能恢复体力,从下一个小时开始才能睡着。 为了身体健康,这头牛希望遵循生物钟,每天采用相同的睡觉计划。 另外,因为时间是连续的,即每一天的第 N 个小时和下一天的第 1 个小时是相连的(N 点等于 0 点),这头牛只需要在每 N 个小时内休息够 B 个小时就可以了。 请你帮忙给这头牛安排一个睡觉计划,使它每天恢复的体力最多。 输入格式 第 2…N+1 行,第 i+1 行包含一个整数 Ui。 输出格式 数据范围 法二:破环成链(复制一倍在末尾)* 在一条环形公路旁均匀地分布着 N 座仓库,编号为 1∼N,编号为 i 的仓库与编号为 j 的仓库之间的距离定义为 dist(i,j)=min(|i−j|,N−|i−j|),也就是逆时针或顺时针从 i 到 j 中较近的一种。 每座仓库都存有货物,其中编号为 i 的仓库库存量为 Ai。 在 i 和 j 两座仓库之间运送货物需要的代价为 Ai+Aj+dist(i,j)。 求在哪两座仓库之间运送货物需要的代价最大。 输入格式 第二行包含 N 个整数 A1∼AN。 输出格式 数据范围 5 1 8 6 2 5 输出样例: 15 类似于滑动窗口,用单调队列维护 给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。 Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。 输入格式 接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 i 到 j 的距离(记为 a[i,j])。 对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。 输出格式 数据范围 求把 N×M 的棋盘分割成若干个 1×2 的的长方形,有多少种方案。 例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。 如下图所示: 输入格式 每组测试用例占一行,包含两个整数 N 和 M。 当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。 输出格式 数据范围 转载题解
一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。
小智也想收服其中的一些小精灵。
然而,野生的小精灵并不那么容易被收服。
对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。
当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。
当小智的精灵球用完时,狩猎也宣告结束。
我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。
如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。
小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。
现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。
请问,小智该如何选择收服哪些小精灵以达到他的目标呢?
输入格式
输入数据的第一行包含三个整数:N,M,K,分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。
之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。
输出格式
输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。
数据范围
0
10 100 57 10
2 40
2 50
1 20
4 20
3 30
10 100 5
8 110
12 10
20 10
5 200
1 110
0 100
#include
完全背包
#include
自然数拆分
至少拆分成 2 个数的和。
求拆分的方案数 mod2147483648 的结果。#include
AcWing 280.陪审团
#include
区间dp
AcWing 282.石子合并
第一行一个数 N 表示石子的堆数 N。
输出一个整数,表示最小代价。
1≤N≤300#include
AcWing 283.多边形
输入包含两行,第一行为整数 N。
输出包含两行,第一行输出最高分数。
3≤N≤50,
数据保证无论玩家如何操作,顶点上的数值均在 [−32768,32767] 之内。#include
树形dp
AcWing 285.没有上司的舞会
第一行一个整数 N。
输出最大的快乐指数。
1≤N≤6000,
−128≤Hi≤127#include
AcWing 286.选课
输入文件的第一行包括两个整数 N、M(中间用一个空格隔开)其中 1≤N≤300,1≤M≤N。
输出一个整数,表示学分总数。#include
环形与后效性处理
AcWing 288. 休息时间
第 1 行输入两个空格隔开的整数 N 和 B。
输出一个整数,表示恢复的体力值。
3≤N≤3830
2≤B#include
AcWing 289.环路运输
第一行包含一个整数 N。
输出一个整数,表示最大代价。
1≤N≤106,
1≤Ai≤107
输入样例:
#include
状态压缩dp
AcWing 91. 最短Hamilton路径
第一行输入整数 n。
输出一个整数,表示最短 Hamilton 路径的长度。
1≤n≤20
0≤a[i,j]≤107#include
AcWing 291. 蒙德里安的梦想
输入包含多组测试用例。
每个测试用例输出一个结果,每个结果占一行。
1≤N,M≤11#include
#include
单调队列优化dp
AcWing 298. 围栏
#include
斜率优化
AcWing 300. 任务安排1
#include
301. 任务安排2
#include