Save the Trees


题目描述:

一道特别好的题.给出1e5个树.这个树就是路边种的树= =.每个树有一个类型编号1 ≤ typei ≤ 1e5.每个数还有一个高度. 现在要把这些树分组.只有连在一起的才能分组.并且一个组内不能出现两个相同类型的树.一个组内的价值是最高高度的树的高度.要求最后分出的组,价值和最小.求最小的价值和.

题解:

显然是dp.关键看状态怎么定.为了好转移,我们可以定dp[i]指以i结束并且强制i是最后一组的最高的树.但是这样发现不能够算出最终答案.因为枚举最后一个树属于谁的最高树下的分组的时候没有办法排除重复类型.
于是只能定义dp[i]只考虑完前i棵树之后的最小结果.关键是怎么转移.
先预处理出来i最多往前面能够延伸到哪里.然后本来是暴力枚举.但是我们其实用到单调队列的话是可以一个阶段一个阶段的知道i属于的那一段价值是多少.那么知道了i属于的那一段的价值,我们就想能不能预处理出来前面需要暴力统计的部分. 发现只有在单调队列里面放的树才会影响最后一段的取值.并且显然dp[i]是一个单调不减的.i越小越好. 那么我们单调队列x处的肯定一直延伸到y+1,然后才用dp[y]的值(y在队列中是x的前面,y的高度比x的大).然后把这个值整体当做set的权值放到set里面.log(n)的转移.

重点:

(1)想到强制i是最大值.但是不能算出结果
(2)用解决完i定义dp.转移的时候有分段的感觉,再加上dp[i]的贪心性质,利用单调队列+set就能快速转移

代码:
#include <cstdio>
#include <cstring>
#include <set>

using namespace std;

const int N = 100005;

int type[N], h[N], g[N], f[N], co[N], q[N];

int main() {
    int T, ca = 0;
    scanf("%d", &T);
    while (T--) {
        int n;
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
            scanf("%d%d", &type[i], &h[i]);
        g[1] = 1;
        memset(co, 0, sizeof(co));
        co[type[1]] = 1;
        int j = 1;
        for (int i = 2; i <= n; ++i) {
            if (co[type[i]]) {
                do {
                    co[type[j]] = 0;
                    ++j;
                } while (co[type[i]]);
            }
            co[type[i]] = 1;
            g[i] = j;
        }
        f[1] = h[1];
        multiset<int> S;
        int head = 0, tail = 1;
        q[head] = 1;
        S.insert(h[1]);
        for (int i = 2; i <= n; ++i) {//这里是关键
            while (tail - head && h[q[tail - 1]] <= h[i]) {
                if (tail - head == 1)
                    S.erase(S.find(h[q[tail - 1]] + f[g[i - 1] - 1]));
                else S.erase(S.find(h[q[tail - 1]] + f[q[tail - 2]]));
                --tail;
            } 
            q[tail++] = i;
            if (tail - head == 1)
                S.insert(h[i] + f[g[i - 1] - 1]);
            else
                S.insert(h[i] + f[q[tail - 2]]);
            for (int k = g[i - 1]; k < g[i]; ++k) {
                S.erase(S.find(f[k - 1] + h[q[head]]));
                if (k - 1 == q[head]) {
                    ++head;
                    S.erase(S.find(f[k - 1] + h[q[head]]));
                }
                S.insert(f[k] + h[q[head]]);
            }
            f[i] = *S.begin();
        }
        printf("Case %d: %d\n", ++ca, f[n]);
    }
    return 0;
}

/*#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;
int n, head, tail;
int front[N], have[N], type[N], height[N], q[N];
multiset <int > st; 
int main() {
    int T;
    scanf("%d", &T);
    for (int cas = 1; cas <= T; ++cas) {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d%d", &type[i], &height[i]);
        }
        front[1] = 1;
        have[type[1]] = 1;
        int pos = 1;
        for (int i = 2; i <= n; ++i) {
            while (have[type[i]]) {
                have[type[pos]] = 0;
                pos++;
            }
            front[i] = pos;
            have[type[i]] = 1;
        }
        st.clear();
        memset(f, 0, sizeof f);
        f[1] = height[1];
        st.insert(height[1]);
        head = tail = 1;
        q[head] = 1;
        for (int i = 2; i <= n; ++i) {
            while (head <= tail && height[i] >= height[q[tail]]) {
                if (head < tail) {
                    st.erase(st.find(height[q[tail]] + f[q[tail - 1]]))
                }
                else {
                    st.erease(st.find(height[q[tail]] + f[front[i - 1] - 1]]));
                }
                tail--;
            }
            q[tail++] = height[i];
            if (tail < head) {
                st.insert(height[i] + f[front[i] - 1]);
            }
            else {
                st.insert(height[i] + f[q[tail]]);
            }
        }
    }
    return 0;
}*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值