Description
- 有
n
n
n个人,第
i
i
i个人会每隔
a
i
a_i
ai上班
a
i
a_i
ai天,初始都上班
a
i
a_i
ai天。
- 现在每一天可以选择至多一个上班的人发奖牌,求最少多少天能够让每个人都拿到
k
k
k个奖牌。
-
n
≤
18
,
k
,
a
i
≤
1
e
5
,
n\le18,k,a_i\le1e5,
n≤18,k,ai≤1e5,
Solution
- 首先二分时间。
- 考虑
n
n
n很小,每一天对应一个人,可以直接hall定理,
2
n
2^n
2n枚举人暴力判定。
- 由于最少
2
e
5
∗
n
2e5*n
2e5∗n就可以做完,所以值域不大,可以暴力扫,再用高维前缀和,容斥一下计算贡献。
- 暴力预处理每一天的人的集合
O
(
2
e
5
n
∗
n
)
O(2e5n*n)
O(2e5n∗n)
- 二分判定
O
(
(
2
e
5
n
+
2
n
n
)
l
o
g
2
e
5
n
)
O((2e5n+2^nn)\ log\ 2e5n)
O((2e5n+2nn) log 2e5n)
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define maxn 20
#define maxm 10000005
using namespace std;
int n,m,i,j,k,a[maxn],s[maxm],f[1<<maxn],c[1<<maxn];
int check(int p){
memset(f,0,sizeof(int)*(1<<n));
for(i=1;i<=p;i++) f[s[i]]++;
for(i=0;i<n;i++) for(j=0;j<1<<n;j++) if (!(j>>i&1))
f[j^(1<<i)]+=f[j];
for(i=0;i<1<<n;i++) if (p-f[i]<(n-c[i])*m)
return 0;
return 1;
}
int main(){
freopen("ceshi.in","r",stdin);
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++) scanf("%d",&a[i]);
int l=n*m,r=2e5*n,mid,ans=r;
for(i=1;i<=r;i++) for(j=1;j<=n;j++)
s[i]^=((i-1)%(2*a[j])+1<=a[j])<<j-1;
for(i=1;i<1<<n;i++) c[i]=c[i>>1]+(i&1);
while (l<=r){
mid=(l+r)>>1;
if (check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
printf("%d",ans);
}