题意
有 N N N个人( N ≤ 18 N\leq 18 N≤18),第 i i i个人出现 A i A_i Ai天然后消失 A i A_i Ai天( A i ≤ 1 0 5 A_i\leq 10^5 Ai≤105),循环下去。每天可以给某一个出现的人发一块奖牌,问最少要多少天才能让任意一个人都有至少 K K K块奖牌( K ≤ 1 0 5 K\leq 10^5 K≤105)。
分析
首先二分答案,这样就转化为了一个判定性问题。
考虑一个简单的网络流建模,
S
S
S向每个人连一条容量为
K
K
K的边,每个人向他出现的一天连一条容量为
1
1
1的边,每一天向
T
T
T连一条容量为
1
1
1的边。这样只要最大流满流就有解了。
考虑到人数很小,可以把所有天分成
2
N
2^N
2N类,代表每个人是否在这一天出现。这样我们就可以把同一类的若干天合并成一个点,向
T
T
T连一条容量为这一类天的总数的边。
考虑答案的范围。显然答案
≥
N
K
\geq NK
≥NK。按顺序给每一个人发
K
K
K块奖牌,每个人至多花费
2
K
2K
2K天,因此答案的一个上界是
2
N
K
2NK
2NK,只有
3.6
×
1
0
6
3.6\times10^6
3.6×106级别。因此我们可以预处理出每一天是属于哪一类的。
考虑怎样快速计算这个网络流。把网络流转化为最小割来考虑,相当于在左边割掉一些人,右边割掉一些天使得
S
S
S和
T
T
T不联通。如果一个人没有被左边割掉,那右边包含这个人的所有天必须被割掉。因此我们可以枚举左边割掉了哪些人,这样右边割掉的天就已经确定下来了,不难发现右边割掉的边的权值和是一个子集和的形式,可以通过
F
W
T
FWT
FWT求高维前缀和在
2
N
N
2^NN
2NN的时间复杂度内预处理出来。判断最小割是否为
N
K
NK
NK即可。
时间复杂度:
2
N
N
log
(
N
K
)
2^NN\log(NK)
2NNlog(NK)
代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=20,M=1050005;
int n,m,k,l,r,mid,ans,a[N],b[M],c[M],sta[N*M];
bool check(){
for(int i=0;i<m;i++){
b[i]=0;
c[i]=c[i>>1]+(i&1);
}
for(int i=0;i<mid;i++){
b[sta[i]]++;
}
for(int i=1;i<m;i<<=1){
for(int j=0;j<m;j+=(i<<1)){
for(int k=j;k<j+i;k++){
b[k+i]+=b[k];
}
}
}
int res=n*k;
for(int i=0;i<m;i++){
if(b[m-1]-b[i]+c[i]*k<res){
return false;
}
}
return true;
}
int main(){
scanf("%d%d",&n,&k);
m=1<<n;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
l=n*k,r=2*n*k,ans=r;
for(int i=0;i<r;i++){
for(int j=0;j<n;j++){
if(~(i/a[j])&1){
sta[i]|=1<<j;
}
}
}
while(l<=r){
mid=(l+r)/2;
if(check()){
ans=mid;
r=mid-1;
}else{
l=mid+1;
}
}
printf("%d\n",ans);
return 0;
}