【洛谷 P1484 种树】 解题报告

本文详细解析洛谷P1484种树问题,介绍如何利用动态规划与贪心算法解决种树最大获利问题,通过优化算法避免超时,并使用大根堆和链表维护数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【洛谷 P1484 种树】 解题报告

题目描述

cyrcyr今天在种树,他在一条直线上挖了n个坑。这n个坑都可以种树,但为了保证每一棵树都有充足的养料,cyrcyr不会在相邻的两个坑中种树。而且由于cyrcyr的树种不够,他至多会种k棵树。假设cyrcyr有某种神能力,能预知自己在某个坑种树的获利会是多少(可能为负),请你帮助他计算出他的最大获利。

输入输出格式
输入格式:

第一行,两个正整数n,k。

第二行,n个正整数,第i个数表示在直线上从左往右数第i个坑种树的获利。

输出格式:

输出1个数,表示cyrcyr种树的最大获利。

输入输出样例
input

6 3
100 1 -1 100 1 -1

output

200

说明

对于20%的数据,n<=20。

对于50%的数据,n<=6000。

对于100%的数据,n<=500000,k<=n/2,在一个地方种树获利的绝对值在1000000以内。

解法:

可以直接DP:

:f[i][j]表示种到第i棵树且种了j棵的最大获利,则f[i][j]=max(f[i-1][j],f[i-2][j-1]+a[i])。
然而这样会T掉,因为最后的数据太大了。

所以就有了贪心的解法:

k=1的时候,显然选最大的(假设为a[i])。
当K=2而的时候,要么选一个与a[i]不相邻的数,要么选a[i-1],a[i+1]。
所以a[i-1],a[i+1]必须同时选或者同时不被选(可以玄学证明一下)。

所以就可以用一个大根堆来维护。

  1. 每次取出堆顶,然后ans加上这个值,再把这个数原来的位置上的值变为相邻两个数的和再减去这个位置上的值(表示合并一下这3个数,为了以后要反悔的时候用)。
  2. 把取出来的那个数原来的数的相邻两个数标记一下,表示已经不能用了,但是为了防止这两个相邻的数的和会大于原来的数加上除了相邻的数意外任意的数的和。所以就把把它原来的数的相邻的数的和减去原来的数的值如堆(为了可以反悔)。
  3. 如此这么操作进行k次,假如堆顶的值小于0,那么就直接退出(加上的话肯定亏了啊)
  4. 但是这样有个小(大)问题,怎么样才能快速的求出每个数相邻的数呢?(已经进行了多次),所以要用一个链表来维护,即分别表示它的左边相邻的数,右边相邻的数。

Talk is cheap,show me your codes:

#include<bits/stdc++.h>
using namespace std;
#define ll long long

const int maxn=500005;
int n,k,use[maxn];
ll ans,a[maxn];
int l[maxn],r[maxn];

struct Node
{
    ll mark;
    int num;	
};

class Heap
{
    public:
        Node heap[maxn];
        int cnt;
        
        void Set()
        {
            memset(heap,0,sizeof(heap));
            cnt=0;
        }
        
        void Up(int p)
        {
            while(p>1){
                if(heap[p].mark > heap[p/2].mark){
                    swap(heap[p],heap[p/2]);
                    p/=2;
                }
                else break;
            }
        }
        
        void Insert(ll x,int num)
        {
            heap[++cnt].mark=x;
            heap[cnt].num=num;
            Up(cnt);
        }
        
        void Down(int p)
        {
            int s=p<<1;
            while(s<=cnt){
                if(heap[s].mark<heap[s+1].mark && s<cnt) s++;
                if(heap[s].mark > heap[p].mark){
                    swap(heap[s],heap[p]);
                    p=s;s=p<<1;
                }
                else break;
            }
        }
        
        void Remove(int k)
        {
            heap[k].mark=heap[cnt].mark;
            heap[k].num=heap[cnt--].num;
            Up(k);Down(k);
        }
};

Heap d;

/*
inline void Debug()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int x;scanf("%d",&x);
        d.Insert(x,i);		
    }
    for(int i=1;i<=n;i++){
        printf("%d %d\n",d.heap[1].num,d.heap[1].mark);
        d.Remove(1); 
    }
}
*/

inline void Input()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        d.Insert(a[i],i); 
        l[i]=i-1;r[i]=i+1;
    }
}

inline void Work()
{
    r[0]=1;l[n+1]=n;
    for(int i=1;i<=k;i++){//3
        while(use[d.heap[1].num]) d.Remove(1); 
        Node now=d.heap[1];d.Remove(1);
        if(now.mark < 0) break;
        ans+=now.mark;
        int x=now.num;
        a[x]=a[l[x]]+a[r[x]]-a[x];//1.
        now.mark=a[x];
        use[l[x]]=use[r[x]]=1;//2.
        l[x]=l[l[x]];r[l[x]]=x;
        r[x]=r[r[x]];l[r[x]]=x;//4.
        d.Insert(now.mark,now.num);//2. 
    }
    printf("%lld\n",ans);
}

int main()
{
//	Debug();
    Input();
    Work();
    return 0;
}
人生的经验:
  1. 假如代码比较长的时候,建议把不同的步骤写成函数的形式,这样方便Debug(别问我怎么知道的)。
  2. 这个反悔这个操作是很骚的,在贪心的时候发现会错过最优解的时候,可以想一想怎么反悔。
目前提供的引用内容并未涉及洛谷 P8306 的具体题目解析或代码实现。然而,可以基于一般性的编程竞赛问题解决思路来推测可能的解答方向。 以下是关于如何处理类似问题的一些通用方法: ### 可能的题目背景 假设洛谷 P8306 是一道算法类题目,通常这类题目会涉及到数据结构、字符串操作或者动态规划等内容。如果没有具体的题目描述,可以从以下几个方面入手分析并提供解决方案。 --- ### 字符串解析与变量赋值 如果该题目类似于引用中的其他题目(如 P1597),可能是要求对输入字符串进行解析,并完成某些特定的操作。以下是一个类似的 C 实现框架,用于解析字符串并对变量 `a`、`b` 和 `c` 进行更新[^1]: ```c #include <stdio.h> #include <string.h> int main() { int arr[3] = {0}; // 存储 a, b, c 的值 char str[266]; scanf("%s", str); for (int i = 0; i < strlen(str); i++) { if (str[i] >= 'a' && str[i] <= 'c') { // 判断是否为有效变量名 if (i + 2 < strlen(str) && str[i + 2] == '=') { // 检查是否有 "=" 符号 if (str[i + 3] >= '0' && str[i + 3] <= '9') { // 如果右侧是数字 arr[str[i] - 'a'] = str[i + 3] - '0'; } else if (str[i + 3] >= 'a' && str[i + 3] <= 'c') { // 如果右侧是另一个变量 arr[str[i] - 'a'] = arr[str[i + 3] - 'a']; } } } } printf("%d %d %d\n", arr[0], arr[1], arr[2]); return 0; } ``` 此代码片段适用于简单的字符串解析场景,其中需要根据条件修改数组中存储的值。 --- ### 排序相关问题 如果洛谷 P8306 更接近于排序问题(如引用提到的快速排序[^2]),则可以考虑使用标准库函数或手写排序逻辑。例如,在 C 中可以通过 `qsort()` 函数实现快速排序: ```c #include <stdio.h> #include <stdlib.h> void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; } int partition(int arr[], int low, int high) { int pivot = arr[high]; int i = (low - 1); for (int j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return (i + 1); } void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } } int main() { int arr[] = {10, 7, 8, 9, 1, 5}; int n = sizeof(arr) / sizeof(arr[0]); quickSort(arr, 0, n - 1); for (int i = 0; i < n; i++) printf("%d ", arr[i]); return 0; } ``` 这段代码实现了手动版的快速排序,适合用来解决需要自定义比较规则的情况。 --- ### 动态规划或其他高级技巧 对于更复杂的题目(如种树问题[^4]),可能会需要用到动态规划或者其他优化策略。下面是一段伪代码示例,展示如何通过状态转移方程解决问题: ```cpp #include <iostream> #include <vector> using namespace std; const int MAXN = 1e5 + 5; bool used[MAXN]; int solve(vector<pair<int, pair<int, int>>> tasks, int limit) { sort(tasks.begin(), tasks.end(), [&](pair<int, pair<int, int>> a, pair<int, pair<int, int>> b) -> bool { return a.second.first < b.second.first; }); int result = 0; for (auto &[_, range] : tasks) { int count = 0; for (int pos = range.first; pos <= range.second; ++pos) { if (!used[pos]) { used[pos] = true; count++; if (count == _) break; } } result += min(_, count); } return result; } int main() { vector<pair<int, pair<int, int>>> input_data = {{...}}; // 输入数据初始化 cout << solve(input_data, ...) << endl; return 0; } ``` 以上代码展示了如何利用布尔标记数组记录已使用的资源,并通过贪心法最大化目标值。 --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值