题目描述
在二维平面上给定 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。
- 把子集内点排序,检查是否满足(x,y)单调不减;
- 计算把这些点串起来一共需要多少新增点 cost;
- 如果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
思路:
- 点先排序:按 x 升序,x 相同 y 升序。j > i 保证pj.x>=pi.x。
- dfs(i,used):当前以第 i 号原始点作为序列结尾,已经消耗used个新增点,能选出最多多少个原始点。
- 从 i 向后枚举每一个j(j>i),满足yj>=yi,计算连接 i 到 j 需要补充的点need=dx+dy-1。
- 如果总消耗 (used+need \le k),就可以跳去 j:dfs(j,used+need)+1(原始点数目 + 1)。
- memoiused记忆化缓存,避免重复递归计算。
- 初始:每一个点作为起点,初始消耗used=0,调用dfs(i,0)得到该起点最多原始点数量。
- 输出写的 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;
}