[ARC106E] Medals
[ARC106E] Medals
题意
有 \(n \le 18\) 个人,每个人会工作 \(a_i\) 天,休息 \(a_i\) 天,然后又工作 \(a_i\) 天,以此类推。每天你可以给至多一个在岗的人发奖。问每个人至少发了 \(k\) 个奖至少需要几天。
思路
参考了洛谷题解区。自认为写得详细,不需要前置知识也可以看懂因为我都不会那些前置知识。
首先答案下界显然是 \(nk\),上界则是 \(2nk\),当你先给第一个人发满 \(k\) 个奖,显然最多使用 \(2k\) 天,然后再发第二个人,一个一个人发,因此上界是 \(2nk\)。
求最小答案不好求,考虑二分答案,做判定问题:能否在 \(mid\) 天完成发奖。
每天都可以从若干个人中选择一个人发奖,这种无法贪心,感觉没有多项式算法,考虑建二分图。
左部是人,右部是天(笑点解析:右部是天)。每个人和他能上班的天连边。我们把一个人拆成 \(k\) 个人(笑点有些密集,以下不做解析)。题目相当于做边覆盖,每个左部点必须覆盖至少一次,每个右部点至多被覆盖一次。判定能否达成。
把右边没有用的点扔掉,相当于问能否做一个完美匹配(即边覆盖满足每个点都恰好覆盖了一次)
有 Hall 定理:一个二分图可以做完美匹配,当且仅当某一部的任意一个点集 \(S\),于这个点集的点有连边的右部点集为 \(T\),都有 \(|T| \ge |S|\)。
必要性显然,因为如果不满足 Hall 定理,那么有某一个 \(|S| > |T|\),那么 \(S\) 里面一定存在点是没有被匹配的。充分性也显然,因为如果满足 Hall 定理但是最大匹配不是完美匹配,那么左部点全集对应的 \(|S| < |T|\),矛盾。
因此我们要判定能否存在完美匹配,只需要判断是否对于所有人的子集,都有对应的天的集合大小大于等于人的集合。
因为我们刚刚把一个人拆成了 \(k\) 个人,这 \(k\) 个人的边是一样的,因此我们不拆他们了。
也就是人的集合是 \(S\),对应的天的集合是 \(T\),判定是否 \(k|S| \le |T|\)。
每个人都有 \(O(nk)\) 条边连向天。可以预处理每天有哪些人上班,这些上班的人叫做每天的上班集合,时间复杂度 \(O(n^2k)\)。
求 \(|T|\),就是求有多少天的上班集合和 \(S\) 是有交的,这个求有交的不好求,不好优化。正难则反,我们该求有多少天的上班集合是和 \(S\) 无交的,即有多少天的上班集合是包含于 \(S\) 关于 \(n\) 个人的补集的。
这个包含于就好求了。可以做个 \(n\) 维的高维前缀和,每维只有 \(0/1\) 代表这个人上不上班。然后对所有天的上班集合做个前缀和,然后对所有的 \(S\),数 \(S\) 的补集有多少个天的上班集合包含于它,就直接前缀和 \(O(1)\) 查询。
这里求前缀和是 \(O(2^nn)\) 的,枚举 \(S\) 是 \(O(2^n)\) 的,还要二分答案,再加上预处理每天的上班集合,总时间是 \(O(n^2k+2^nn \log (nk))\)。
code
代码还是很好写的。
#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace characteristic {
constexpr int N=20,K=1e5+7,V=3e5;
int n,k;
int a[N];
int d[2*N*K];
int s[V];
int l,r;
bool check(int m) {
memset(s,0,sizeof(s));
rep(i,1,m) s[d[i]]++;
rep(i,0,n-1) rep(j,0,(1<<n)-1) if((j>>i)&1) s[j]+=s[j^(1<<i)];
rep(i,0,(1<<n)-1) {
if(m-s[(1<<n)-1-i]<k*__builtin_popcount(i)) return 0;
}
return 1;
}
void main() {
sf("%d%d",&n,&k);
l=n*k,r=l<<1;
rep(i,0,n-1) sf("%d",&a[i]);
rep(i,0,n-1) {
for(int j=1,op=1;j<=r;j++) {
d[j]|=(op<<i);
if(j%a[i]==0) op^=1;
}
}
while(l<r) {
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
pf("%d\n",r);
}
}
int main() {
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("my.out","w",stdout);
#endif
characteristic :: main();
}
本文来自博客园,作者:liyixin,转载请注明原文链接:https://www.cnblogs.com/liyixin0514/p/18540510