【AtCoder】ARC70D No Need -DP&贪心

传送门:arc70d


题解

我好菜啊。。。一道 600pts 600 p t s 的题想了这么久。。。
首先观察出最后的答案必然为值最小的前几个数(光这个都思考了很久QWQ),简单证明一下:
先将 a a 排序。
一个数ai unnecessary u n n e c e s s a r y 当且仅当所有含有这个数的“好”集合的值 k+ai ≥ k + a i 。这样不好想,我们换一种说法: ai a i unnecessary u n n e c e s s a r y 当且仅当所有不含有这个数的“坏”集合的值 <kai < k − a i <script type="math/tex" id="MathJax-Element-8">< k-a_i</script>。
现在假设 ax(x>1) a x ( x > 1 ) unnecessary u n n e c e s s a r y 的,则不包含 ax a x 的“坏”集合最大值 <kax < k − a x <script type="math/tex" id="MathJax-Element-12">< k-a_x</script>,那么可以证明, ai(1i<x) a i ( 1 ≤ i < x ) 均在这个值最大的“坏”集合当中,采用反证法:若存在一个数 ai(1i<x) a i ( 1 ≤ i < x ) 不在这个值最大的“坏”集合中,而这个值 <kax < k − a x <script type="math/tex" id="MathJax-Element-15"> kax+ai<k k − a x + a i < k ,这个不在“坏”集合中的数完全可以加入集合中使集合的值更大,与“值最大的‘坏’集合”这一定义矛盾。所以 ai(1i<x) a i ( 1 ≤ i < x ) 都在这个“坏”集合中。
设这个“坏”集合的值为 val v a l ,由 val<kax v a l < k − a x val v a l 中含有 ai(1i<x) a i ( 1 ≤ i < x ) val+axai<kax+axai v a l + a x − a i < k − a x + a x − a i ,设 val=val+axai v a l ′ = v a l + a x − a i ,则 val<kai v a l ′ < k − a i 。因为 val v a l 是不含 ax a x 的最大“坏集合”,我们删去 ai a i 加上 ax a x 的过程保证了将 val v a l ′ 最大化,所以证明了若 ax a x unnecessary u n n e c e s s a r y 的, ai(1i<x) a i ( 1 ≤ i < x ) 也一定是 unnecessary u n n e c e s s a r y 的。
所以只需要找出最大的 unnecessary u n n e c e s s a r y ax a x ,答案就是 x x
valx(1xn)表示不包含 ax a x 的值最大的“坏”集合的值, sumx s u m x 表示 ax a x 的前缀和, f(x,v) f ( x , v ) 表示 axn a x − n 这个后缀中所有数的集合所能表示出的小于 v v 的最大值。
由上面的证明得到:valx=sumx1+f(x+1,ksum),依次枚举判断 valx v a l x 是否 <kax < k − a x <script type="math/tex" id="MathJax-Element-46">< k-a_x</script>即可。 f(x,v) f ( x , v ) 可用树状数组维护。


代码

#include<bits/stdc++.h>
using namespace std;
const int N=5050;
int n,k,a[N],f[N],mx,sum,tre[N];

inline void ad(int x,int val)
{for(;x<=k;x+=(x&(-x))) tre[x]=max(tre[x],val);}

inline int get(int x)
{
    int re=0;
    for(;x;x-=(x&(-x))) re=max(re,tre[x]);
    return re;
}

int main(){
    int i,j,res;
    scanf("%d%d",&n,&k);
    for(i=1;i<=n;++i) {scanf("%d",&a[i]);a[i]=min(a[i],k);sum+=a[i];}
    sort(a+1,a+n+1);
    for(f[0]=1,mx=0,i=n;i>=1;--i){
        sum-=a[i];if(sum<k) res=get(k-sum-1);
        mx=min(mx+a[i],k);
        for(j=mx;j>=a[i];--j) if(!f[j] && f[j-a[i]]){
            ad(j,j);f[j]=1;
        }
        if(sum>=k) continue;
        if(sum+res<k-a[i]) break;
    }
    printf("%d\n",i);       
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值