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\n",Count);
57 
58     return 0;
59 }
posted @ 2020-01-18 17:53  AE酱  阅读(181)  评论(0编辑  收藏  举报