luoguP3287 [SCOI2014] 方伯伯的玉米田

发布时间 2023-11-15 18:42:45作者: HL_ZZP

题目描述

方伯伯在自己的农田边散步,他突然发现田里的一排玉米非常的不美。这排玉米一共有 NN 株,它们的高度参差不齐。方伯伯认为单调不下降序列很美,所以他决定先把一些玉米拔高,再把破坏美感的玉米拔除掉,使得剩下的玉米的高度构成一个单调不下降序列。方伯伯可以选择一个区间,把这个区间的玉米全部拔高 11 单位高度,他可以进行最多 KK 次这样的操作。拔玉米则可以随意选择一个集合的玉米拔掉。问能最多剩多少株玉米,来构成一排美丽的玉米。

输入格式

第一行包含两个整数 n,Kn,K,分别表示这排玉米的数目以及最多可进行多少次操作。第二行包含 nn 个整数,第 ii 个数表示这排玉米,从左到右第 ii 株玉米的高度 aiai

输出格式

输出一个整数,最多剩下的玉米数。

输入输出样例

输入 #1
3 1
2 1 3
输出 #1
3

说明/提示

100%100% 的数据满足:2≤N<1042N<104,2≤K≤5002K500,1≤ai≤50001ai5000。

 

题面简洁得不像是noip的题。。
我自己第一次想的时候弄错了一个点,题面里说拔高的起点和终点都由我们决定,但是事实上,终点可以锁死是n,这个是可以证明绝对不亏的
但是我没想到。。。
然后就没有思路了

我以前明明做过这种可以拔高的lis的题面的啊,为什么这次想不到?
太奇怪了
那状态就很明显了
f[i][k]表示已经拔高了k次,每次的起点不超过i的时候以a[i]结尾的子串最长有多长

对于每一个不满足不下降条件的a[i],我们有两个转移,一个是不提高直接拔掉,一个是提高,答案++,k+=一个数字
用从后向前的转移,对于每一个f[i][j],枚举所有1<=k<=i,然后再枚举所有的1<=h<=j,来进行不拔高的转移和拔高的转移
但是这个条件限制的也太多了点吧。。。
这怎么维护啊
emmmmm
好像直接一个前缀和类似的求最大值就好了?
我想想看
好像没问题啊,我试试看
怎么可能这么简单。。
我的转移方程是有条件的,拔高的次数是由转移的位置和当前位置共同决定的
又有二位偏序的味道了
但是是三维的啊
还有一个k的要求
可以做到吗?
想想看
要把方程写下来,不然不好想
果然,写下来方程之后清晰多了。。
dp方程还是要写出来的,特别是优化。。。
没有方程什么都没有
我想把式子写上来。。。但是我真的是不会用typora啊啊啊啊啊啊啊
我学学看吧,能把这个思考过程写上来,对我的以后的帮助真的很大,可以帮助我快速思考和确定思路
好像是只要单个变量的值都可以维护啊
确实哎,这好像又是一种新的维护了
在还没有使用二位偏序的时候,我们可以暂时用一个log的复杂度为代价来直接无视一个转移范围限制条件
这个被无视的条件在代码里面的反映就是使用树状数组的二位偏序啊
卧槽可以的

好好好,这道紫题写的我好爽啊,刚刚好是那种我垫垫脚能够到的难度,收获很大


错了,方程都是有问题的。。
好难啊,md
dp的转移方程似乎是倾向于把范围扩大,因为维护一个区间所有数值的信息其实比维护更多个更小范围的区间的信息更加方便
唉,我的理解还是有不小的偏差的。。
再写一题吧
说说我看完博客之后的思路
其实三个条件,我们就是直接二维树状数组就好了
其中一个条件是因为阶段的划分而天然满足的

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
    char c=getchar();int a=0,b=1;
    for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
    for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
inline int lowbit(int x)
{
    return x&(-x);
}
int n,k,a[10001],tr[6001][501],tot1,tot2,ans,mx;
void add(int x,int i,int y)
{
    for(;x<=mx+k;x+=lowbit(x))
        for(int j=y;j<=k+1;j+=lowbit(j))
            tr[x][j]=max(tr[x][j],i);
}
inline int ask(int x,int y)
{
    int ans=0;
    for(;x;x-=lowbit(x))
        for(int i=y;i;i-=lowbit(i))
            ans=max(ans,tr[x][i]);
    return ans;
}
int main()
{
    n=read(),k=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        mx=max(mx,a[i]);
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=k;j>=0;j--)
        {
            int x=ask(a[i]+j,j+1)+1;
            ans=max(ans,x);
            add(a[i]+j,x,j+1);
        }
    }
    cout<<ans<<endl;
    return 0;
}
//2 1 1 2

我其实不是很明白,就是添加的和转移的式子为什么是一样的
倒序的循环很好理解,防止一个点被转移两次

他们的方程思路和我的一模一样,但是表述不是很严谨,利用了贪心的思路扩大的枚举的范围,更加方便维护了
草,方程错了

两个点
1.方程的内部尽量简洁,条件往外面写
2.利用贪心扩大范围,有的时候可能会更好维护
这题要是把范围写的太死。。就好难搞了