方伯伯的玉米田

9 篇文章 0 订阅
4 篇文章 0 订阅

例题:方伯伯的玉米田

一位大佬的链接(有关二维树状数组)

前言

关于我做这道题的心路历程:

 1.我在某个晚上准备练习DP题,看到方伯伯的玉米田这
 道题,难度适宜,知识简单,准备做一做。一个晚上过去
 了,我除了推出了他每一次会抬高最右边的玉米外,还推
 出了一大堆错误结论,例如一定会从已有的上升序列中的
 左端点抬高啦。甚至还想,这不会是贪心题吧.
 2.第二个晚上,我搞出了一大堆DP方程,就剩最后怎么
 处理高度的问题。结果这个状态转移怎么也想不出。最后
 看题解,发现越看越糊涂,无聊之中点开了一个题解的链
 接。这是我转机的开始(要是没点开,可能我一辈子都做
 不出)
    首先我发现他的题解写错了 然后我发现我把题看错了。

接下来是关于做这道题的思路

不少大佬的题解都一开始说明f[i][j]指的是第i个点被覆盖j次的最大不下降序列。这其实就是这道题的枚举思路,然而并不需要写在代码中(Ps.很多题都是这样),这也就要是说,dp方程与他无关。

这就会有疑问:为什么无关?

还记得背包优化吗?

滚动数组使得一维消失了,因而从DP方程看上去,与枚举到哪一个物品无关。本题同样如此。这也使得在枚举剩余维度是一定要注重顺序。

接下来,将用到性质一:每次考虑抬高一段区间,我们都视这段区间的右端点为n

证明:

考虑从 l 开始抬高,当我们抬高到r,会对原有的上升子序列有两个影响
1. 1-l的序列中的每一个数,它右边比它大的数与抬
高前相比,有增多的趋势。我们称这对产生最长不下降子序列
有有利影响
2. r-n的每一个数,他前边比它小的数有减少趋势。我们
称这对产生最长不下降子序列有不利影响。

这就可以发现,我们把r定为n时,没有不利影响。

这就相当于,我们到一个点,就可以枚举它被抬高了几次。我们把区间的抬高转化为了点的抬高。

性质2:当前枚举到的点i,在它为j次抬高时,以他为终点的不下降子序列由前边任意一个比它抬高后矮的点x+1取max得来

这个看上去容易得到,其实把这个想通了,这道题做了一半。
首先,在实际写法中,需测判这个点x抬高次数是否小于j;
否则对于这样一组数据 4 1 2 1 4 3会出错。

实际上有了这两个性质后,我们就可以初步列出方程

f[i][j][k]=maxp=1i1f[p][j][k](j+k<j+k,k<k) f [ i ] [ j ] [ k ] = max p = 1 i − 1 f [ p ] [ j ′ ] [ k ′ ] ( j ′ + k ′ < j + k , k ′ < k )

可以看出这是一个三维转移方程,然而,我们发现
1. 我们大的高度总是由小的高度推出
1. 我们不关注每个位置具体的情况,我们只要求得最终答案即可。

介于上述两点,我们可以考虑降维。但我们从大的高度枚举到小的高度时,因为对每个位置的枚举是有序,先枚举的大高度又不会影响后枚举的小高度。
转移方程即为下述

f[j][k]=maxj+k<j+kf[j][k](k<k) f [ j ] [ k ] = max j ′ + k ′ < j + k f [ j ′ ] [ k ′ ] ( k ′ < k )

做到这里,已有了一个欠优的代码,考虑求max,可用二维树状数组优化后,降到 (log2n)2 ( l o g 2 n ) 2

关于二维树状数组,前面的大佬链接有详细的解释,也可以看我的另一篇博客。我们可以类比一维。一维树状数组可以让我们在 log2n l o g 2 n 的时间进行修改与查询线状的一条前缀和,二维树状数组能让我们在 (log2n)2 ( l o g 2 n ) 2 内进行一个矩形面积的查询。

#include<bits/stdc++.h>
#define ll long long
#define RG register
#define IL inline
#define MAXN 10000
using namespace std;
IL int read(){
    RG int data=0;
    RG char ch=0;
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch<='9'&&ch>='0'){
        data=data*10+ch-'0';
        ch=getchar();
    }
    return data;
}
IL int lowbit(int k){return k&(-k);}
int n,k,mh;
int btree[MAXN][MAXN],h[MAXN];
void fix(RG int ii,RG int jj,RG int x)
{
    for(RG int i=ii;i<=mh+k;i+=lowbit(i))
    for(RG int j=jj;j<=k+1;j+=lowbit(j))
    btree[i][j]=max(x,btree[i][j]);
}
int query(RG int ii,RG int jj){
    RG int ret=0;
    for(RG int i=ii;i;i-=lowbit(i))
    for(RG int j=jj;j;j-=lowbit(j))
    ret=max(ret,btree[i][j]);
    return ret;
}
int main()
{
    n=read(),k=read();
    //cout<<n<<' '<<k<<endl;
    for(RG int i=1;i<=n;++i){
        h[i]=read();
        mh=max(mh,h[i]);
    }
    RG int ans=0;
    for(RG int i=1;i<=n;++i){
        for(RG int j=k;j>=0;--j)
        {
            RG int x=query(h[i]+j,j+1)+1;
            ans=max(ans,x);
            fix(h[i]+j,j+1,x);
        }
    }
    printf("%d",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一个相对复杂的问题,需要使用树的相关知识和Java编程技术实现。下面是一个简单的实现示例,供您参考: ``` import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; public class FamilyTree { // 家族成员类 static class Member { String name; // 姓名 Member father; // 父亲 List<Member> children; // 孩子 public Member(String name) { this.name = name; this.children = new ArrayList<>(); } // 添加孩子 public void addChild(Member child) { children.add(child); child.father = this; } } public static void main(String[] args) { // 初始族谱 Member root = new Member("祖先"); Member m1 = new Member("父亲"); Member m2 = new Member("叔叔"); Member m3 = new Member("伯伯"); Member m4 = new Member("儿子1"); Member m5 = new Member("儿子2"); Member m6 = new Member("侄子1"); Member m7 = new Member("侄子2"); Member m8 = new Member("侄孙1"); Member m9 = new Member("侄孙2"); Member m10 = new Member("孙子1"); Member m11 = new Member("孙子2"); root.addChild(m1); root.addChild(m2); root.addChild(m3); m1.addChild(m4); m1.addChild(m5); m2.addChild(m6); m2.addChild(m7); m6.addChild(m8); m7.addChild(m9); m4.addChild(m10); m5.addChild(m11); // 构建家族成员Map,便查询 Map<String, Member> members = new HashMap<>(); members.put(root.name, root); members.put(m1.name, m1); members.put(m2.name, m2); members.put(m3.name, m3); members.put(m4.name, m4); members.put(m5.name, m5); members.put(m6.name, m6); members.put(m7.name, m7); members.put(m8.name, m8); members.put(m9.name, m9); members.put(m10.name, m10); members.put(m11.name, m11); // 测试查询和修改功能 Scanner scanner = new Scanner(System.in); while (true) { System.out.println("请输入要查询或修改的家族成员姓名:"); String name = scanner.nextLine(); Member member = members.get(name); if (member == null) { System.out.println("找不到该成员!"); continue; } System.out.println("该成员的父亲是:" + (member.father == null ? "无" : member.father.name)); System.out.println("该成员的孩子有:"); for (Member child : member.children) { System.out.println(child.name); } System.out.println("请选择要进行的操作:1-新增孩子,2-退出"); int option = scanner.nextInt(); scanner.nextLine(); if (option == 1) { System.out.println("请输入要新增孩子的姓名:"); String childName = scanner.nextLine(); Member child = new Member(childName); member.addChild(child); members.put(childName, child); System.out.println("新增孩子成功!"); } else if (option == 2) { break; } else { System.out.println("无效的操作!"); } } } } ``` 在这个示例中,我们使用了树的数据结构来存储家族成员的族谱,每个成员都是一个节点,有父亲和孩子。构建家族成员Map可以便地查询和修改家族成员。在主函数中,我们先构建了一个初始的家族成员,然后通过Scanner从键盘输入要查询或修改的成员姓名,查询该成员的父亲和孩子,并提供了新增孩子的功能,直到用户选择退出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值