CSP-J 2022 上升点列 题解

题目描述

在二维平面上给定 n 个整数点,允许自由添加最多 k 个整数点。选出若干点组成序列:

相邻两点欧几里得距离等于 1,只能向右(+1,0)或者向上(0,+1);

横纵坐标均单调不减;

求序列的最大长度。

序列的长度 = 原始选出来的点数量 + 我们自由添加的点数量。

关键点理解

序列只能向右、向上走,所以整个序列的点满足 x 不下降、y 不下降。

已有两点j(x1,y1),必须满足x2>=x1,y2>=y1。

两点欧几里得距离:d=(x2-x1)+(y2-y1);想要把两点连成连续路径,中间需要补dx+dy-1个新增点。

没有用完的新增点,可以全部接在序列的末尾,继续向右 / 向上延伸,每一个新增点都贡献序列长度。

20分暴力DFS

思路:

枚举原始点的全部子集,子集数量2^n。

  1. 把子集内点排序,检查是否满足(x,y)单调不减;
  2. 计算把这些点串起来一共需要多少新增点 cost;
  3. 如果cost<=k,代表这个子集可行。总长度 = 子集大小+(k-cost)。
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n,k;
vector<pair<int,int>> p;
int maxn=0;

void check(const vector<int> &c)
{
    if(c.empty()) return ;
    vector<pair<int,int>> cur;
    for(int idx:c)
    {
        cur.push_back(p[idx]);
    }
    sort(cur.begin(),cur.end());
    //单调性的检查
    for(int i=1;i<cur.size();i++)
    {
        if(cur[i].first<cur[i-1].first||cur[i].second<cur[i-1].second)
        {
            return;
        }
    }
    //计算需要添加的点数  欧几里得距离:d=(x2-x1)+(y2-y1)
    int cost=0;
    for(int i=1;i<cur.size();i++)
    {
        int dx=cur[i].first-cur[i-1].first;
        int dy=cur[i].second-cur[i-1].second;
        cost+=(dx+dy-1);//需要补的点数
    }
    if(cost<=k)
    {
        maxn=max(maxn,(int)c.size());
    }
}

void dfs(int idx,vector<int> &c)
{
    if(idx==n)
    {
        check(c);
        return ;
    }
    dfs(idx+1,c);//选了
    c.push_back(idx);
    dfs(idx+1,c);//没选
    c.pop_back();
}

int main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        int x,y;
        cin>>x>>y;
        p.push_back({x,y});
    }
    vector<int> c;
    dfs(0,c);
    cout<<maxn+k;
    return 0;
}

AC解一:记忆化dfs

思路:

  1. 点先排序:按 x 升序,x 相同 y 升序。j > i 保证pj.x>=pi.x。
  2. dfs(i,used):当前以第 i 号原始点作为序列结尾,已经消耗used个新增点,能选出最多多少个原始点。
  3. 从 i 向后枚举每一个j(j>i),满足yj>=yi,计算连接 i 到 j 需要补充的点need=dx+dy-1。
  4. 如果总消耗 (used+need \le k),就可以跳去 j:dfs(j,used+need)+1(原始点数目 + 1)。
  5. memoiused记忆化缓存,避免重复递归计算。
  6. 初始:每一个点作为起点,初始消耗used=0,调用dfs(i,0)得到该起点最多原始点数量。
  7. 输出写的 maxn+k。
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n,k;
vector<pair<int,int>> p;
int memo[505][505];
int maxn=0;

int dfs(int i,int used)
{
    if(memo[i][used]!=-1) return memo[i][used];
    int best=1;
    for(int j=i+1;j<n;j++)
    {
        if(p[j].second<p[i].second) continue;
        int dx=p[j].first-p[i].first;
        int dy=p[j].second-p[i].second;
        int need=dx+dy-1;
        if(used+need<=k)
        {
            best=max(best,dfs(j,used+need)+1);
        }
    }
    return memo[i][used]=best;
}

int main()
{
    cin>>n>>k;
    for(int i=0;i<n;i++)
    {
        int x,y;
        cin>>x>>y;
        p.push_back({x,y});
    }
    sort(p.begin(),p.end());
    vector<int> c;
    memset(memo,-1,sizeof(memo));
    for(int i=0;i<n;i++)
    {
        maxn=max(maxn,dfs(i,0));
    }
    cout<<maxn+k;
    return 0;
}

AC解二:DP

DP定义:dpiused:以第 i 个原始点作为序列最后一个点,连接过程已经消耗used个新增点,序列中原始点的数量。

状态转移

j 在 i 的前面,满足pi.y >= pj.y。

两点之间连接需要新增点:

need = (pi.x-pj.x)+(pi.y-pj.y)-1枚举之前已经消耗的新增点数目used,当used+need <= k,代表资源够用:

dpiused+need=max(dpiused+need,dpjused+1)含义:原来以 j 结尾,消耗used个点;连上 i 之后,多消耗need个点,原始点数量 + 1。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 505;
const int MAXK = 105;

int n,k;
vector<pair<int,int>> p;
int dp[MAXN][MAXK];

int main()
{
    cin>>n>>k;
    for(int i=0;i<n;i++)
    {
        int x,y;
        cin>>x>>y;
        p.emplace_back(x,y);
    }
    sort(p.begin(),p.end());

    memset(dp,0,sizeof(dp));
    for(int i=0;i<n;i++)
    {
        dp[i][0]=1;
        for(int j=0;j<i;j++)
        {
            if(p[i].second < p[j].second) continue;
            int dx = p[i].first-p[j].first;
            int dy = p[i].second-p[j].second;
            int need = dx+dy-1;

            for(int used=0;used+need<=k;used++)
            {
                if(dp[j][used]==0) continue;
                dp[i][used+need]=max(dp[i][used+need], dp[j][used]+1);
            }
        }
    }

    int ans=0;
    for(int i=0;i<n;i++)
    {
        for(int used=0;used<=k;used++)
        {
            if(dp[i][used]==0) continue;
            ans = max(ans, dp[i][used]+(k-used));
        }
    }
    cout<<ans<<endl;
    return 0;
}
相关推荐
lvts_cs23 分钟前
化工产业规划对城市发展的具体指导作用
算法·动态规划
AI服务老曹1 小时前
水位识别算法接入AI视频分析平台全流程与误报优化指南(附接口字段与排查表)
人工智能·算法·音视频
江畔柳前堤1 小时前
消息队列:从直觉到精通的完整认知地图
开发语言·人工智能·算法·机器学习·ruby on rails·scala
ai产品老杨1 小时前
明火识别算法接入AI视频分析平台全流程与误报优化指南(附接口字段与排查表)
人工智能·算法·音视频
讨厌吃蛋黄酥1 小时前
📚408数据结构|最短路径算法一篇彻底搞懂!BFS、Dijkstra、Floyd别再傻傻分不清了
算法
Escalating_xu2 小时前
【C++ string 上篇】从字符串基础到容量管理、迭代器与经典算法题
java·c++·算法
Keven_112 小时前
算法札记:ACM中将高精度算法融于题目的模板
算法·acm·精度
小七在进步2 小时前
数据结构:快速排序
数据结构·算法·排序算法
闻缺陷则喜何志丹3 小时前
【计算几何 第十一章】凸包:混合物
数学·算法·计算几何·凸包·混合物·凸组合