[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();
}
posted @ 2024-11-11 20:32  liyixin  阅读(3)  评论(0编辑  收藏  举报