<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">题意:</span>
有n个仓库(最多100个),m个管理员(最多30个),每个管理员有一个能力值P(接下来的一行有m个数,表示每个管理员的能力值)
每个仓库只能由一个管理员看管,但是每个管理员可以看管k个仓库(但是这个仓库分配到的安全值只有p/k,k=0,1,...),
每个月公司都要给看管员工资,雇用的管理员的工资即为他们的能力值p和,问,使每个仓库的安全值最高的前提下,使的工资总和最小。
输出最大安全值,并且输出最少的花费。
思路:
看别人的是用两次DP 我用了二分求最大的最小安全系数 然后就是一个01背包了
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int INF=2147483647;
int n,m,sum,w[50],v[50],d[100005];
int p[50],cnt,cnt1;
struct Node{
int p,k,an,id;
bool operator < (const Node &a) const {
return an < a.an;
}
}temp[100000];
int add(int p,int k,int &cnt,int id)
{
temp[cnt].p=p;
temp[cnt].k=k;
temp[cnt].id=id;
temp[cnt].an=p/k;
cnt++;
}
int pan(int m)
{
int vis[50]={0},he=0;
for(;m<cnt;m++){
if(vis[temp[m].id]) continue;
he+=temp[m].k;
vis[temp[m].id]=1;
if(he>=n) return 1;
}
return 0;
}
void init(int s,int &he)
{
int vis[50]={0};
he=0;
for(int i=s;i<cnt;i++){
if(vis[temp[i].id]) continue;
vis[temp[i].id]=1;
he+=temp[i].k;
w[cnt1]=temp[i].k;
v[cnt1]=-temp[i].p; //令背包物品的价值为负值 这样求出来的最大值的相反数就是最小值
cnt1++;
}
}
void solve(int r)
{
int ans=INF;
for(int i=0;i<=sum;i++) d[i]=-INF;
d[0]=0;
for(int i=0;i<cnt1;i++)
for(int j=sum;j>=w[i];j--)
if(d[j-w[i]]!=-INF) d[j]=max(d[j],d[j-w[i]]+v[i]);
for(int i=n;i<=sum;i++)
ans=min(ans,-d[i]);
printf("%d %d\n",temp[r].an,ans);
}
int main()
{
while(scanf("%d%d",&n,&m))
{
if(!n&&!m) break;
cnt=sum=cnt1=0;
for(int i=0;i<m;i++) {
scanf("%d",&p[i]);
sum+=p[i];
int t=min(p[i],n);
add(p[i],t,cnt,i);
for(int j=t-1;j>=1;j--){
if(p[i]/j==temp[cnt-1].an) continue;
add(p[i],j,cnt,i);
}
}
if(sum<n) { printf("0 0\n"); continue; }
sort(temp,temp+cnt);
int l=0,r=cnt-1;
while(l<=r){
int m=l+(r-l)/2;
if(pan(m)) l+=1;
else r-=1;
}
for(l=r;l>=0&&temp[l].an==temp[r].an;l--){} //这里应该找符合条件的最左边的那个
r=temp[l].an==temp[r].an?l:l+1;
init(r,sum);
solve(r);
}
}