1. 程式人生 > 其它 >Identical Day 題解(思維)

Identical Day 題解(思維)

題目連線

題目大意

給你一個長度為\(n(n\leq1e5)\)\(01\)

求最少使得多少個\(1\)變為\(0\)後這個串的價值小於\(k(k\le1e10)\)

串的價值為所有連續為\(1\)的串的價值

一段長度為\(len\)連續為\(1\)的價值為\(len*(len+1)/2\)

例如\(0111101\)的價值為\(\frac{4\times5}{2}+\frac{1\times2}{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("\n I am here\n");
#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\n",ans);
    return 0;
}