CF1886E I Wanna be the Team Leader

发布时间 2023-10-13 13:20:59作者: Martian148

贪心 #动态规划

Question

Monocarp 是一家大型 IT 公司的负责人

他有 \(m\) 个项目个项目需要完成,第 \(i\) 个项目的难度为 \(b_i\)

他的团队离有 \(n\) 个程序员,第 \(j\) 个程序员的耐受能力为 \(a_j\)

Monocarp 需要分配这些项目,需要满足

  • 每个程序员最多分配 \(1\) 个项目
  • 每个项目至少需要分配给一个程序员
  • 如果有 \(k\) 个程序员分配给第 \(i\) 个项目,那么每个程序员的容忍值不能小于(可以大于等于) \(\frac{b_i}{k}\)

帮助 Monocarp 有效的分配这些项目,如果有多个答案,输出任意一个即可

\((1 \le n 2\times 10^5; 1 \le m \le 20)\)

Solution

由于一段程序中最弱的那个程序员的 \(a_j\) 不能低于 \(\frac{b_i}{k}\) ,所以贪心的思想,我们把程序员按照 \(a_j\) 从大到小排序后,一段程序的程序员肯定是连续的

image.png

然后考虑程序的排序,发现 \(m\) 特别的小,自然而然想到状态压缩DP

定义 \(F[s]\) 表示当前程序的状态是 \(s\) 需要的最少程序员个数(从大到小排序后)

考虑如何转移,对于每个新加进来的项目 \(i\) 下一个状态就是 \(s|(1<<i)\)

那么,对于每个项目,需要从上一个 \(F[s]\) 往后找,找一段区间 \([l,r]\),使得 \(b[r]>a[i]/(r-l+1)\) ,然后把这个 \(r\) 赋值给 \(F[s|(1<<i)]\) ,此时的时间复杂度是 \(nm\times 2^m\)

考虑如何优化,其实对于每个项目 \(i\) 从第 \(j\) 个程序员开始,到那个程序员结束是可以预处理出来的,因为对于一个区间 \([l,r]\)\(l\) 增加的时候 \(r\) 必然不会减小

题中,我定义了 \(mn[i][j]\) 表示第 \(i\) 个项目,前面的项目已经用了 \(j\) 个程序员的所需程序员最少人数

Code

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int N,M;
int F[1<<19+5],mn[25][maxn],lst[1<<19+5];
inline int read(){
    int ret=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
    while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}
int a[maxn],id[maxn],b[25],INF;
vector<int> ans[maxn];
bool cmp(int i,int j){return a[i]>a[j];}
int main(){
    // freopen("E.in","r",stdin);
    // freopen("E.out","w",stdout);
    N=read();M=read();
    for(int i=1;i<=N;i++) a[i]=read(),id[i]=i;
    for(int i=1;i<=M;i++) b[i]=read();
    sort(id+1,id+1+N,cmp);
    
    memset(F,127,sizeof F);INF=F[0];
    for(int i=1;i<=M;i++){
        int r=1;
        for(int l=0;l<=N;l++){
            r=max(r,l+1);
            while(r<=N&&a[id[r]]*(r-l)<b[i]) ++r;
            mn[i][l]=(r==N+1?INF:r);
        }
    }
    
    F[0]=0;
    for(int s=0;s<(1<<M);s++)if(F[s]!=INF)
        for(int i=1;i<=M;i++)
            if(!((s>>(i-1))&1)&&F[s|(1<<(i-1))]>mn[i][F[s]]){
                F[s|(1<<(i-1))]=mn[i][F[s]];
                lst[s|(1<<(i-1))]=s;
            }
    
    if(F[(1<<M)-1]==INF){printf("NO\n");return 0;}
    printf("YES\n");
    
    int now=(1<<M)-1;
    while(now){
        int i=__builtin_ctz(now^lst[now]);
        for(int j=F[lst[now]]+1;j<=F[now];j++)
            ans[i+1].push_back(id[j]);
        now=lst[now];
    }
    for(int i=1;i<=M;i++){
        printf("%d",ans[i].size());
        for(int x:ans[i]) printf(" %d",x);
        printf("\n");
    }
    return 0;
}

Summary

  • 看到 \(m \le 20\) 自然而然想到状压DP

  • __builtin_ctz(x) 返回 \(x\) 的二进制下末尾的 \(0\) 的个数