CF1913C Game with Multiset 題解

心海秋的墨木仄發表於2024-04-10

翻譯

初始時你有一個空序列,共 \(m\) 次操作,每次操作讀入兩個數 \(t_i\)\(v_i\),分為以下兩種操作:

  1. \(t_i=1\) 時,在空序列中加入 \(2^{v_i}\) 這一元素。(此時 \(0 \le v_i \le 29\)
  2. \(t_i=2\) 時,詢問是否存在:取當前序列的某些元素,使它們的的和等於 \(v_i\)(此時 \(0 \le v_i \le 10^9\))。若存在輸出 YES,否則輸出 NO

思路

學過倍增求 LCA 的人都知道,每次倍增向上跳時的,會選擇從大到小跳。在本題中,當判斷是否存在時,從大到小判斷選擇的數,與倍增求 LCA 有異曲同工之妙。

最開始本來打算用一個陣列 \(q\) 存值,然後每次詢問時從大到小暴力查詢,假設當前陣列中有 \(k\) 個數,則單次複雜度為 \(O(k)\),明顯複雜度不夠優秀(吃了一發 TLE)。

轉變思路,對於每個操作一,考慮用 \(q\)\(v_i\) 的數量。再透過前面的小結論,每次查詢時,透過查詢出當前 \(v_i\) 可減去的最大 \(x \times 2^j\)\(j\) 表示當前列舉到的冪次,\(x\)\(1 \le x \le q_j\))表示對應的倍數。

然後自信提交後又 TLE 了。

再次發現雖然冪次只有 \(30\)。但 \(q_j\) 可能會很大,單次列舉任然有極高的複雜度,所以這裡直接使用二分列舉。

程式碼

#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#define mod 998244353
#define ll long long
#define fr(i , a , b) for(ll i = a ; i <= b ; ++i)
#define fo(i , a , b) for(ll i = a ; i >= b ; --i)
using namespace std;
//priority_queue <ll> q;
//priority_queue <ll , vector<ll> , greater<ll>> q;
inline ll QuickPow(ll a , ll b)
{
    if(a == 1 || b == 0)
    {
        return 1;
    }
    ll k = QuickPow(a , b >> 1);
    if(b & 1)
    {
        return k * k * a;
    }
    return k * k;
}
ll m , opt , x;
ll q[100005] , top;
ll max_num;
signed main()
{
    // freopen("in.in" , "r" , stdin);
    // freopen("out.out" , "w" , stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> m;
    while(m--)
    {
        cin >> opt >> x;
        if(opt == 1)
        {
            q[x]++;
            max_num += QuickPow(2 , x);
        }
        else
        {
            if(x > max_num)
            {
                cout << "NO" << '\n';
                continue;
            }
            fo(i , 29 , 0)
            {
                if(!q[i])
                {
                    continue;
                }
                ll l = 0 , r = q[i] , ans = 0;
                ll mid;
                ll now = QuickPow(2 , i);
                while(l <= r)
                {
                    mid = (l + r) / 2;
                    if(now * mid > x)
                    {
                        r = mid - 1;
                    }
                    else
                    {
                        ans = mid;
                        l = mid + 1;
                    }
                }
                x -= now * ans;
                if(x <= 0)
                {
                    break;
                }
            }
            if(x == 0)
            {
                cout << "YES" << '\n';
            }
            else
            {
                cout << "NO" << '\n';
            }
        }
    }
    return 0;
}

相關文章