[bzoj3174][Tjoi2013]拯救小矮人

DP+贪心


前言

说实话,我感觉网上大多数文章讲这篇都是在口胡,我发现znber同学的证明也是明显错误的(也许是我太蒻了).但有幸的是我遇上了这篇文章http://blog.csdn.net/commonc/article/details/51693992
他跨了一年,做出了这道题,心疼QAQ。但我觉得他这篇还是没有多详细,搜索引擎也不容易先搜到他,于是就写了这篇辣鸡题解QAQ。


题意

我一开始一直纠结于“一个小矮人走后剩下的小矮人能不能重新组成梯子”。后面发现如果你把确定出去的小矮人直接依次排列,就可以不用重新组成了。所以这是一个没有意义的问题QAQ.所以我们现在的问题是,确定一个排列,
使得最后的能够出去的人最多(根据我刚才所说,能出去的人一定排在最后)(我这里最后代表着梯子的最上面).


求解第一步.贪心

我们把最后的那一部分(即要出去的人)拿出来看,发现他们的这个序列的ai+bi是单调递减的.具体证明如下:
如果有Ai与Aj相邻,且Ai在Aj的前面,Ai+Bi < Aj + Bj,此时的序列合法,那么我们需要证明把它们交换后此时的序列仍然合法.假设Ai前面的Ax的和是Sum.那么我们先看Ai,Ai+Sum+Bi是大于井深H的,我们交换后,Ai手伸出的高度变为Ai+Sum+Bi+Aj>Ai+Sum+Bi>H.显然依然合法.然后我们看Aj,他出去时高度变为Aj+Bj+Sum>Ai+Bi+Sum>H.也是合法的。


求解第二步.Dp

我们把原来序列从小到大排了以后,显然就能进行Dp了,我们可以注意到,对于一个矮人来说,如果他要被选的话,除了前面选了的矮人。其他所有的矮人都要被他踩在脚底,显然其他所有矮人高度最矮为好,这样他就能够踩得更高。那我们f[i][j]表示前i个位置选j个矮人,这j个矮人的最小高度和.然后就很好转移辣。还可以注意的是,这玩意儿是可以省掉前面一维的QAQ


Code

#include<cstdio>
#include<algorithm>
#include<cstring>
const int N = 2e3 + 7, INF = 0x3f3f3f3f;
char B[1 << 12], *S = B, *T = B;
char getchar2 () { return S == T && (T = (S = B) + fread (B, 1, 1 << 12, stdin), S == T) ? -1 : *S++; }
char pr[1 << 12], *pt = pr;
void putchar2 (char x) { if (pt - pr == 1 << 12) fwrite (pr, 1, 1 << 12, stdout), pt = pr; *pt++ = x; }
void G (int &num) {
    static char a;
    for (a = getchar2 (); a > '9' || a < '0'; a = getchar2 ()) ;
    for (num = 0; a >= '0' && a <= '9'; a = getchar2 ()) num = (num << 3) + (num << 1) + a - '0'; 
}
void print (int x) {
    static char stk[12]; static int top;
    if (!x) { putchar2 ('0'); putchar2 ('\n'); return ; }
    while (x) stk[++top] = x % 10 + '0', x /= 10;
    while (top) putchar2 (stk[top--]); putchar2 ('\n');
}

void IO () {
    freopen ("3174.in", "r", stdin);
    freopen ("3174.out", "w", stdout);
}

int n, f[N], h, sum;

struct item {
    int x, y;
    void init () { G (x); G (y); sum += x; }
    bool operator < (const item &rhs) const { return x + y < rhs.x + rhs.y; }
}s[N];

int main () {
//  IO ();
    memset (f, 0x3f, sizeof (f));
    G (n); for (int i = 1; i <= n; ++i) s[i].init (); G (h);
    std :: sort (s + 1, s + n + 1); f[0] = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = i; j; --j) {
            if (sum - f[j - 1] + s[i].y >= h) f[j] = std :: min (f[j], f[j - 1] + s[i].x);
        }
    }
    for (int i = n; ~i; --i) if (f[i] != INF) {
        printf ("%d\n", i);
        return 0;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值