第十三届蓝桥杯省赛A组题解传送门
题目大意
给定一个长度为$n$的正整数序列$a$
现在你有一次机会,将其中连续的$K$个数修改成任意一个相同值
请你计算如何修改可以使修改后的数列的最长不下降子序列最长,请输出这个最长的长度
最长不下降子序列是指序列中的一个子序列,子序列中的每个数不小于在它之前的数
解题思路
以$dp1[i]$表示从前往后以$a[i]$结尾的最长不下降子序列的长度
以$dp2[i]$表示从前往后以$a[i]$开头的最长不下降子序列的长度
我们最后的答案显然会由三部分组成
如果第一部分为$dp1[i]$,第二部分就是$k$,第三部分就是$max(dp2[j]),i+k+1\leq j\leq n且a[j]\geq a[i]$
第三部分我们可以用线段树快速得到
处理$dp1$和$dp2$的过程就是一个普通的求最长不下降子序列的过程
二分,权值树状数组,权值线段树,都是$O(nlogn)$的复杂度,随便选一个即可
如果写权值线段树的话,要先离散化一下,不然可能炸空间
具体代码
#include <bits/stdc++.h>
const int N = 1e5 + 10;
int n, m, k, ans;
int a[N];
int b[N]; // 用于离散化的数组
int dp1[N]; // dp1[i]表示从前往后以a[i]结尾的最长不下降子序列的长度
int dp2[N]; // dp2[i]表示从前往后以a[i]开头的最长不下降子序列的长度
int find(int x) //返回整数a[i]在b数组中的下标
{
int l = 1, r = m;
while (l < r)
{
int mid = l + r >> 1;
if (b[mid] >= x)
r = mid;
else
l = mid + 1;
}
return l;
}
struct
{
int maxv;
} seg[N * 4];
void pushup(int id)
{
seg[id].maxv = std::max(seg[id << 1].maxv, seg[id << 1 | 1].maxv);
}
void build(int id, int l, int r)
{
if (l == r)
seg[id].maxv = 0;
else
{
int mid = l + r >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
pushup(id);
}
}
void change(int id, int l, int r, int pos, int val)
{
if (l == r)
seg[id].maxv = std::max(seg[id].maxv, val);
else
{
int mid = l + r >> 1;
if (pos <= mid)
change(id << 1, l, mid, pos, val);
else
change(id << 1 | 1, mid + 1, r, pos, val);
pushup(id);
}
}
int query(int id, int l, int r, int ql, int qr)
{
if (l == ql && r == qr)
return seg[id].maxv;
int mid = l + r >> 1;
if (qr <= mid)
return query(id << 1, l, mid, ql, qr);
else if (ql >= mid + 1)
return query(id << 1 | 1, mid + 1, r, ql, qr);
else
return std::max(query(id << 1, l, mid, ql, mid), query(id << 1 | 1, mid + 1, r, mid + 1, qr));
}
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> n >> k;
for (int i = 1; i <= n; ++i)
std::cin >> a[i], b[i] = a[i];
std::sort(b + 1, b + n + 1); //排序
if (n == k)
{
std::cout << n << '\n';
return 0;
}
m = 1;
for (int i = 2; i <= n; i++) //去重
if (b[i] != b[m])
b[++m] = b[i];
for (int i = 1; i <= n; ++i)
a[i] = find(a[i]);
build(1, 1, m); //建权值线段树
for (int i = 1; i <= n - k; ++i)
{
dp1[i] = query(1, 1, m, 1, a[i]) + 1;
change(1, 1, m, a[i], dp1[i]);
}
build(1, 1, m); // dp1已经处理完,重新建树处理dp2
for (int i = n; i >= k + 1; --i)
{
ans = std::max(ans, dp1[i - k] + k + query(1, 1, m, a[i - k], m));
// 第一段为dp1[i-k],第二段为k,第三段为max(dp2[j]),i+k+1<=j<=n且a[j]>=a[i]
dp2[i] = query(1, 1, m, a[i], m) + 1;
change(1, 1, m, a[i], dp2[i]);
}
// 特殊情况
for (int i = 1; i <= n - k; ++i)
ans = std::max(ans, dp1[i] + k);
for (int i = n; i >= k + 1; --i)
ans = std::max(ans, dp2[i] + k);
std::cout << ans << '\n';
return 0;
}
一年过去了 提高课也看得差不多了 还是不懂这个线段树是怎么写的 令人感叹
555 13号考试了,啥也不会
请问各位大佬线段树在acwing有y总的视频吗,想学一学
有,蓝桥杯辅导课和算法提高课里面都有
可惜我只有基础课。还有不到一周就比赛,看来得放弃线段树了
没事,蓝桥杯考的比较少,这个还是比较难。
这题做了四个小时,没大佬的题解估计还得卡一个小时(哭
%%%%%%%
看题解看了四个小时
4 2
20 15 5 13
结果应该为4吧,但代码的计算结果为3
感谢指出,漏考虑了这种情况,数据太弱了也没测出来 (
最后加上这两行更新即可
你好,请问,这里ans = std::max(ans, dp1[i - k] + k + query(1, 1, m, a[i - k], m));并没有比较a[i-k]和a[j]的大小关系呀~这里不是很懂,求教!谢谢
请问这题二分怎么求dp2[i]啊,二分不是只能求一段区间上的最长上升子序列长度吗
4 4
1 2 3 4
应该是 4,不是0
可能直接加个这个?(这题好几个月了,还不行的话我再仔细研究一下
感谢指出:)
大佬,我想不通我这么写哪里有问题。。。您能帮忙看看吗
#include[HTML_REMOVED]
using namespace std;
const int N=100010;
typedef pair[HTML_REMOVED] PII;
int a[N];
int main()
{
int n,m;
cin>>n>>m;
vector[HTML_REMOVED] q;
for(int i=1;i<=n;i)
{
cin>>a[i];
}
int be=1,ed=0;
for(int i=1;i<=n;i)
{
if(a[i]>a[i-1])
ed=i;
else
{
q.push_back({be,ed});
be=ed=i;
}
}
int res=0;
//for(int i=0;i<q.size();i)
//printf(“%d %d\n”,q[i].first,q[i].second+1);
for(int i=0;i<q.size();i)
{
res=max(res,q[i].second-q[i].first+1);
for(int j=i+1;j<q.size();j++)
{
if(q[j].first-q[i].second<=m)
{
res=max(res,q[j].second-q[i].first+1);
res=max(res,q[j].second-min(q[j].first-m,q[i].first)+1);
res=max(res,max(q[j].second,q[i].second+m)-q[i].first+1);
}
}
}
printf(“%d”,res);
}