CF1996F Bomb

zengmaoyuan發表於2024-08-27

前言

大概是一個經典題,只不過比較難想

思路

首先考慮 \(O(k)\) 的做法,很明顯,每次我們可以選取一個最大值,然後在把他放回優先佇列裡面,只不過這樣不足以透過此題

而我們又發現只要我們知道最後一次選取的數(第 \(k\) 大)是多少,則前面的數全都可以知道(即對於每個 \(a_i\) ,看比這個數大的數,用等差數列求和)

其實第 \(k\) 大的數明顯具有單調性,判斷第 \(k\) 大隻需要看每個 \(a_i\)\(mid\) 大的數的個數即可

在最後統計答案時候需要注意,可能第 \(k\) 大的數有多個,我們可能只需要選取其中的幾個,統計答案時候需要注意

程式碼

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int a[N],b[N];
int n,k;
bool check(int mid)
{
    long long cnt=0;
    for(int i=1;i<=n;i++)
    {
        if(a[i]>=mid)cnt=cnt+1LL*(a[i]-mid)/b[i]+1;
    }
    return cnt>=k;
}
int main()
{
    int _;
    cin>>_;
    while(_--)
    {
        cin>>n>>k;
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<=n;i++) scanf("%d",&b[i]);
        int l=0,r=1e9,best=-1;  
        while(l<=r)
        {
            int mid=(l+r)/2;
            if(check(mid))
            {
                l=mid+1;
                best=mid;
            }
            else r=mid-1;
        }//求第 k 大
	//best 即為第 k 大
        if(best==-1)//特判選不完的情況
        {
            long long ans=0;
            for(int i=1;i<=n;i++)
            {
                long long siz=a[i]/b[i]+1;
                long long h=a[i]-(siz-1)*b[i];
                ans=ans+1LL*(h+a[i])*siz/2;
            }
            cout<<ans<<"\n";
            continue;
        }
        long long ans=0;
        long long sum=0;
        for(int i=1;i<=n;i++)
        {
            if(a[i]<best) continue;
            long long cnt=(a[i]-best)/b[i]+1;
            long long h=(a[i]-(cnt-1)*b[i]);
            if(h==best) h+=b[i],cnt--;//先選擇大於第 k 大的數
            ans=ans+1LL*(h+a[i])*(cnt)/2;
            sum=sum+1LL*cnt;
        }
        //cout<<sum<<"\n";
        ans=ans+1LL*best*(k-sum);//若不足則用第 k 大補齊即可
        cout<<ans<<"\n";
    }
}
/*
1
5 1000
1 2 3 4 5
5 4 3 2 1
*/