本题思路:本题是上升子序列模型中比较简单的模型,分别是从前往后和从后往前走一遍LIS即可。
#include
constexpr int N=110;
int n;
int h[N];
int f[N];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);std::cout.tie(nullptr);
int T;
std::cin>>T;
while(T--){
std::cin>>n;
for(int i=1;i<=n;i++) std::cin>>h[i];
int res=0;
//正向求解LIS问题
for(int i=1;i<=n;i++){
f[i]=1;
for(int j=1;j=1;i--){
f[i]=1;
for(int j=n;j>i;j--)
if(h[j]
本题思路:状态表示f[i]表示以第 i个位置作为当前子序列的右端点的值,g[i]表示以第 i
个位置作为当前子序列的左端点的值。状态属性:f[i]求最长上升子序列的最多景点个数,g[i]求最长下降子序列的最多景点个数。状态计算:f[i]=max(f[i],f[j]+1)(1≤j 求新数组最长上升子序列就是求原数组最长下降子序列答案表示:根据每一个点来划分:ans=max(包含 i的最长上升子序列 + 包含 i的最长下降子序列)所以 ans=max(f[i]+g[i]−1)。
#include
constexpr int N=1010;
int n;
int h[N];
int f[N],g[N];//f用来表示上升子序列 g表示下降子序列
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);std::cout.tie(nullptr);
std::cin>>n;
for(int i=1;i<=n;i++) std::cin>>h[i];
for(int i=1;i<=n;i++){//求出上升子序列
f[i]=1;
for(int j=1;j=1;i--){//求出下降子序列
g[i]=1;
for(int j=n;j>i;j--)
if(h[j]
本题思路:本题与上面一题差不多。
#include
constexpr int N=1010;
int n;
int h[N];
int f[N],g[N];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);std::cout.tie(nullptr);
std::cin>>n;
for(int i=1;i<=n;i++) std::cin>>h[i];
for(int i=1;i<=n;i++){
f[i]=1;
for(int j=1;j=1;i--){
g[i]=1;
for(int j=n;j>i;j--)
if(h[j]
本题思路:这里的我们可以先想想交叉和不交叉的情况下分别画出来的图是什么样子的。我们可以发现,在两座桥不交叉的情况下,有a1
#include
#define x first
#define y second
typedef std::pair PII;
constexpr int N=5010;
int n;
int f[N];
PII line[N];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);std::cout.tie(nullptr);
std::cin>>n;
for(int i=1;i<=n;i++)
std::cin>>line[i].x>>line[i].y;
std::sort(line+1,line+n+1);
int res=0;
for(int i=1;i<=n;i++){
for(int j=0;j
本题思路:本题与上升子序列类似不做详解了。
#include
constexpr int N=1010;
int n;
int a[N];
int f[N];//状态表示:表示当前的状态是以a[i]为结尾的最长上升子序列的和
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);std::cout.tie(nullptr);
std::cin>>n;
for(int i=1;i<=n;i++) std::cin>>a[i];
int res=0;
for(int i=1;i<=n;i++){
f[i]=a[i];//假设当前状态的最大值为a[i];
for(int j=1;j
本题思路:第一问显然每套导弹拦截系统拦截导弹高度为不升子序列。第二问求导弹拦截系统的个数可以转化为求最长上升子序列长度。
证明:
- 1、首先我们把这些导弹分为s组(s即为所求答案)可以看出每一组都是一个不升子序列
- 2、划分完后我们在组一里找一个原序列里以组一的开头点连续的不升子串的最后一个元素,可以知道在组2中一定有一个大与它的点(如果组二中没有的话,那么组二中最高的导弹高度必然小于这个点,而其他的高度都小于这个高度而且是递减或相等的,那么没有必要再开一个组二了,矛盾,所以不存在找不到比他大的点的情况)
- 3、以此类推,对于每一个k组(1<=k
- 4、设最长上升子序列长度为l所求上升子序列为h那么h<=l因为最长上升子序列任意两个不在一组内(如果在同一个组内,则每个组的数不成为一个不生子序列,矛盾)所以l==h。
#include
constexpr int N=1010;
int n;
int a[N];
int f[N];//不升上升子序列
int g[N];//最长上升子序列
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);std::cout.tie(nullptr);
while(std::cin>>a[n]) n++;
int ans1=0;
int ans2=0;
for(int i=0;i
本题思路:贪心的思想: 每次加入到一个末尾元素与当前数最接近的递增或递减序列(末尾元素小于h[u]且最大的或大于h[u]且最小的)所以可以用两个数组up[i]和down[i]分别表示第i个递增序列和和递减序列的末尾元素,然后搜索时,每次按照以上原则尽可能加入到编号靠前的序列就是满足上述思想的方案。dfs迭代:规定一个当前能搜索导弹拦截系统的最大深度depth(即:答案depth),从0开始,每次dfs一遍,如果当前depth下无法拦截所有导弹,则depth++(就是搜索的步长+1)再dfs,直到合法为止(这就是一个迭代加深的过程),则此时的depth即为答案。或者这里可以定义全局变量求解也是可以的。
#include
constexpr int N=55;
int n;
int h[N];
int up[N];//表示以上升子序列的末尾元素
int down[N];//表示以下降子序列的末尾元素
bool dfs(int depth,int u,int su,int sd)//u表示枚举到第u个数h[u],su表示上升子序列的个数,sd表示下降子序列的个数,depth表示迭代的深度
{
if(su+sd>depth) return false;//当前上升序列的个数和下降序列的个数之和如果大于当前所迭代的导弹系统个数,那么此时无法拦截所有的导弹
if(u==n) return true;
bool flag=false;//flag用来表示当前数是否已经加入到一个序列中去
//将h[u]加入到上升子序列中
for(int i=1;i<=su;i++){
//利用贪心的思路每次加入到一个末尾元素与当前数最近的上升子序列中
if(h[u]>up[i]){
int t=up[i];
up[i]=h[u];
if(dfs(depth,u+1,su,sd)) return true;
up[i]=t;//恢复现场
flag=true;
break;//如果满足直接break不需要加入下降子序列中
}
}
if(!flag)
{
up[su+1]=h[u];//如果无法加入当前任何一个递增序列那么此时需要重新开辟一个子序列
if(dfs(depth,u+1,su+1,sd)) return true;
}
flag=false;//flag用来表示当前数是否已经加入到一个序列中去
//将h[u]加入到下降子序列中
for(int i=1;i<=sd;i++){
//利用贪心的思路每次加入到一个末尾元素与当前数最近的下降子序列中
if(h[u]>n,n){
for(int i=0;i>h[i];
int depth=0;//表示迭代加深的深度
while(!dfs(depth,0,0,0)) depth++;
std::cout<
本题思路:状态表示:f[i][j]代表所有a[1 ~ i]和b[1 ~ j]中以b[j]结尾的公共上升子序列的集合;
f[i][j]的值等于该集合的子序列中长度的最大值;状态计算(对应集合划分):首先依据公共子序列中是否包含a[i],将f[i][j]所代表的集合划分成两个不重不漏的子集:不包含a[i]的子集,最大值是f[i - 1][j];包含a[i]的子集,将这个子集继续划分,依据是子序列的倒数第二个元素在b[]中是哪个数:
子序列只包含b[j]一个数,长度是1;子序列的倒数第二个数是b[1]的集合,最大长度是f[i - 1][1] + 1;…子序列的倒数第二个数是b[j - 1]的集合,最大长度是f[i - 1][j - 1] + 1;如果直接按上述思路实现,需要三重循环:
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
f[i][j] = f[i - 1][j];
if (a[i] == b[j])
{
int maxv = 1;
for (int k = 1; k < j; k ++ )
if (a[i] > b[k])
maxv = max(maxv, f[i - 1][k] + 1);
f[i][j] = max(f[i][j], maxv);
}
}
}
然后我们发现每次循环求得的maxv是满足a[i] > b[k]的f[i - 1][k] + 1的前缀最大值。因此可以直接将maxv提到第一层循环外面,减少重复计算,此时只剩下两重循环。最终答案枚举子序列结尾取最大值即可。
#include
constexpr int N=3010;
int n;
int a[N],b[N];
int f[N][N];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);std::cout.tie(nullptr);
std::cin>>n;
for(int i=1;i<=n;i++) std::cin>>a[i];
for(int i=1;i<=n;i++) std::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]=std::max(f[i][j],maxv);
if(a[i]>b[j]) maxv=std::max(f[i-1][j]+1,maxv);
}
}
int res=0;
for(int i=1;i<=n;i++) res=std::max(res,f[n][i]);
std::cout<