题目连接
题目大意
给你一个长度为(n(nleq1e5))的(01)串
求最少使得多少个(1)变为(0)后这个串的价值小于(k(kle1e10))
串的价值为所有连续为(1)的串的价值
一段长度为(len)连续为(1)的价值为(len*(len+1)/2)
例如(0111101)的价值为(frac{4 imes5}{2}+frac{1 imes2}{2}=11)
题目思路
这个题目我一开始想的过于简单了,首先我认为很容易想到优先队列贪心
我用的是每次拿出最长的那一段然后直接中间分隔,用优先队列去维护
但是可以想一下如果一段要分为三段,那么最终我分的比例等于(1:1:2)
但是显然是要(1:1:1) 即三等分,但是我那样第一步是分为两份肯定不行
然后我就不不会了。。。
其实正解和这个差不了太多,就是差值最大
设(cal(a,b))为一段长度为(a)的连续的(1),中间人为变了(b)个的最小价值
将((a,b))这个放入优先队列,那么优先队列只要比较(cal(a,b)-cal(a,b+1))即可
(cal(a,b))这个函数计算也很简单(b)个(0),那么分为(b+1)段
肯定是要等分每一份为(x=(a-b)/(b+1)),而多余了(y=(a-b)\%(b+1))个
那么肯定是给(y)份每一个多(1)个
- ((b+1-y))份长度为$x $
- (y)份长度为(x+1)
以前写过类似的题目,感觉这种题目就是要往差值方面去考虑,不过这个稍微难一点点
代码
#include<bits/stdc++.h>
#define debug printf("
I am here
");
#define fi first
#define se second
#define pii pair<int,int>
typedef long long ll;
const int maxn=1e5+5,inf=0x3f3f3f3f,mod=1e9+7;
const ll INF=0x3f3f3f3f3f3f3f3f;
using namespace std;
ll n,k;
char s[maxn];
ll cal(ll a,ll b){
// 长度为a个1 中间有b个0
ll x=(a-b)/(b+1);
ll y=(a-b)%(b+1);
ll ans=x*(x+1)/2*(b+1-y)+(x+1)*(x+2)/2*y;
return ans;
}
ll dif(ll a,ll b){
return cal(a,b)-cal(a,b+1);
}
struct node{
ll x,y;
friend bool operator<(node a,node b){
return dif(a.x,a.y)<dif(b.x,b.y);
}
};
priority_queue<node> pq;
int main(){
scanf("%lld%lld",&n,&k);
scanf("%s",s+1);
ll len=0,sum=0;
for(int i=1;i<=n;i++){
if(s[i]=='1'){
len++;
}
if(s[i]=='0'||i==n){
if(len!=0){
sum+=len*(len+1)/2;
pq.push({len,0});
}
len=0;
}
}
ll ans=0;
while(sum>k){
ans++;
node temp=pq.top();
pq.pop();
sum-=dif(temp.x,temp.y);
if(temp.x==temp.y+1) continue;
pq.push({temp.x,temp.y+1});
}
printf("%lld
",ans);
return 0;
}