牛客周周练7 C加强版
给你一个\(n,k(n<=400,k<=(n*n/2))\)。再给你一个01串,0表示这天放假,不工作,1表示这天上班,但可以请假。连续上i天班的代价是i,现在问代价不超过k时,最多工作多少天。
设\(dp[i][j]\)为考虑到第i天时,工作了j天。转移的时候,我们只需要考虑最后一段连续1的长度就可以转移了。很简单。
//copied from rk1 @牛客竞赛0331号
#include<bits/stdc++.h>
using namespace std;
int DP[405][405]={0},i,j,k,n,m;
char R[405];
int main(){
scanf("%d%d%s",&n,&m,R+1);
for(i=1;i<=n;i++)DP[0][i]=1e9;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++){
DP[i][j]=DP[i-1][j];
if(R[i]=='0'||j>i) continue;
for(k=1;k<=j&&R[i-k+1]!='0';k++)
DP[i][j]=min(DP[i][j],DP[max(0,i-k-1)][j-k]+k*(k+1)/2);
}
for(i=n;i>=1;i--) if(DP[n][i]<=m) break;
printf("%d\n",i);
}
bonus:\(n<=2e5,k<=1e9\),答案是多少呢?
我们考虑初始的每一段连续的1。这个时候,我们只需要考虑给每一段连续的1再添加一个0的时候,减小的代价就好了(容易想到是均分的时候代价最小),把这些代价扔进一个优先队列里,贪心的选取。容易看出来这个是满足贪心的性质的。
#include<bits/stdc++.h>
#define PB push_back
#define SZ(x) (int)x.size()
using namespace std;
typedef long long LL;
typedef vector<int> VI;
inline int read(){
int res=0, f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N = 200'005;
VI seg;
int n, k;
char s[N];
priority_queue<tuple<LL,int,int>> q;
LL calc(int len, int time){
int x=(len-time)/(time+1);
int y=len-time-x*(time+1);
LL res=0;
res+=1ll*x*(x+1)*(time+1-y)/2;
res+=1l*(x+1)*(x+2)*y/2;
return res;
}
int main(){
n=read(),k=read();
scanf("%s",s+1);
int cur=(s[1]=='1');
for(int i=2;i<=n;++i){
if(s[i]=='0'){
seg.PB(cur);
cur=0;
}else{
++cur;
}
}
if(cur>0)seg.PB(cur);
LL sum=0;
for(int i=0;i<SZ(seg);++i){
sum+=calc(seg[i],0);
q.push(make_tuple((calc(seg[i],0)-calc(seg[i],1)),seg[i],1));
}
while(sum>k){
LL x;int y,z;
tie(x,y,z)=q.top();
q.pop();
sum-=x;
q.push(make_tuple((calc(y,z)-calc(y,z+1)),y,z+1));
}
int res=0;
while(!q.empty()){
LL x;int y,z;tie(x,y,z)=q.top();
q.pop();
res+=y-z+1;
}
cout<<res;
return 0;
}