zoukankan      html  css  js  c++  java
  • AtCoder ARC070D No Need

    题目大意

    给出一个由N个整数构成的集合{ai}和一个整数K,若该集合的某个非空子集中的所有元素之和大于等于K,则称该子集是good的

    若去掉一个数不会对good的集合的个数产生影响,则称该数字为unnecessary的
    请求出在N个数中unnecessary的数的个数

    N,K≤5000,ai≤10^9

    题解

    正难则反,考虑什么时候一个数是necessary的。

    如果存在一个子集,这个子集中所有数之和小于K,但把ai加入子集后所有数之和大于等于K,那么ai是necessary的。

    那么对于每个ai,我们只需要去考虑能不能找到这样一个子集,如果找不到,ai就是unnecessary的。

    不难发现,如果ai≥K,或者某个子集中所有数之和≥K,这些都是无意义的。

    如果给你N个数的集合S,问你它的子集的元素和有哪些可能,这是很好求的,开个bool数组vis,每加入一个数a[j],若vis[i]==true,则vis[i+a[j]]=true即可。

    但是这里存在着限制,我们当前在考虑除了ai剩下的数相加能否小于K,且加上ai后大于等于K,你不能把ai也算进去。

    所以我们设Pre[i][j]表示从第一个数到第i个数这个前缀的vis,设Suf[i][j]表示从第N个数到第i个数这个后缀的vis。

    那么考虑ai时,我们只需考虑能否用Pre[i-1][j]和Suf[i+1][k]凑出一个小于K的数,它加上ai大于等于K

    贪心地想,我们凑出的这个数一定要在小于K的情况下最大

    暴力去找的话每次是K^2的,找N次,时间复杂度是O(N*K^2)的,显然会超时。

    考虑尺取法的思想,维护两个指针p1,p2,p1指向Pre[i-1],p2指向Suf[i+1]

    p1从K-1往0向前移动,p2从0往k-1向后移动

    对于一个固定的p1,p2一直往后移动,直到p1+p2≥K,p2停止移动,然后去移动p1

    因为p1向前移动,移动后p1一定会变小,所以p2继续向后移动

    如果p1+p2<K,且Pre[i-1][p1]==true,Suf[i+1][p2]==true,p1+p2+ai≥K,则ai是necessary的

    这样的话p1,p2的移动都是单调的,单次查找是O(K)的,总时间复杂度是O(NK)的

     1 #include <iostream>
     2 #include <algorithm>
     3 #include <cstring>
     4 #include <cstdio>
     5 using namespace std;
     6 
     7 bool Pre[5002][5002],Suf[5002][5002],Ans[5002];
     8 int Data[5005];
     9 int N,K;
    10 
    11 template<typename elemType>
    12 inline void Read(elemType &T){
    13     elemType X=0,w=0; char ch=0;
    14     while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    15     while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    16     T=(w?-X:X);
    17 }
    18 
    19 int main(){
    20     Read(N);Read(K);
    21     for(register int i=1;i<=N;++i)
    22         Read(Data[i]);
    23     sort(Data+1,Data+N+1);
    24     Pre[0][0]=Suf[N+1][0]=true;
    25     for(register int i=1;i<=N;++i){
    26         for(register int j=0;j<K;++j){
    27             Pre[i][j]=Pre[i-1][j];
    28             Suf[N-i+1][j]=Suf[N-i+2][j];
    29         }
    30         for(register int j=0;j<K;++j){
    31             if(Suf[N-i+2][j] && j+Data[N-i+1]<K) Suf[N-i+1][j+Data[N-i+1]]=true;
    32             if(Pre[i-1][j] && j+Data[i]<K) Pre[i][j+Data[i]]=true;
    33         }
    34     }
    35     for(register int i=1;i<=N;++i){
    36         if(Data[i]>=K){Ans[i]=true;continue;}
    37         int p1=K-1,p2=0;
    38         bool flag=false;
    39         while(p1>=0 && p2<K){
    40             if(!Pre[i-1][p1]){--p1;continue;}
    41             while(p1+p2<K){
    42                 if(Pre[i-1][p1] && Suf[i+1][p2] && p1+p2+Data[i]>=K){
    43                     Ans[i]=true;
    44                     flag=true;
    45                     break;
    46                 }
    47                 ++p2;
    48             }
    49             if(flag) break;
    50             --p1;
    51         }
    52     }
    53     int Count=0;
    54     for(register int i=1;i<=N;++i)
    55         if(!Ans[i]) ++Count;
    56     printf("%d
    ",Count);
    57 
    58     return 0;
    59 }
  • 相关阅读:
    吴裕雄--天生自然ANDROID开发学习:3.3 Handler消息传递机制浅析
    吴裕雄--天生自然ANDROID开发学习:3.2 基于回调的事件处理机制
    吴裕雄--天生自然ANDROID开发学习:3.1.1 基于监听的事件处理机制
    吴裕雄--天生自然ANDROID开发学习:2.6.4 DrawerLayout(官方侧滑菜单)的简单使用
    吴裕雄--天生自然ANDROID开发学习:2.6.3 ViewPager的简单使用
    吴裕雄--天生自然ANDROID开发学习:2.6.2 菜单(Menu)
    吴裕雄--天生自然ANDROID开发学习:2.6.1 PopupWindow(悬浮框)的基本使用
    吴裕雄--天生自然ANDROID开发学习:2.6.0 其他几种常用对话框基本使用
    吴裕雄--天生自然ANDROID开发学习:2.5.9 AlertDialog(对话框)详解
    bzoj 3874: [Ahoi2014&Jsoi2014]宅男计划
  • 原文地址:https://www.cnblogs.com/AEMShana/p/12209767.html
Copyright © 2011-2022 走看看