1. 程式人生 > 其它 >21.7.7 t1

21.7.7 t1

tag:貪心,二進位制


顯然要按位貪心。

對於當前的答案字首,掃一遍求出每個數至少要右移幾次才能貼合當前答案。

對於一個合併樹來說,本來是or在一起然後右移 \(1\),可以看作是每個數先右移若干(合併樹上的深度)次,然後再全部or在一起。

然後可以發現 \(2\) 個需要右移 \(x\) 次的點可以合在一起變成一個需要右移 \(x-1\) 次的點。

於是可以 \(O(w)\) 判斷是否合法。(只要最終能夠合成一個右移 \(0\) 次的點,就合法,其他的點可以直接用刪除操作刪掉)

然後就得到了一個 \(O(Tnw^2)\) 的做法,鬆一鬆可以過。


當然我們不能滿足於 \(O(\)\()\)

的複雜度。

其實是我卡不過去

可以用一個 \(Move\) 陣列(long long)去記錄對於每一個數,每一種右移是否合法。二進位制第 \(i\) 位為 \(1\) 就表示右移 \(i\) 位合法。

初值設為 \(2^{w+1}-1\),然後每次如果確定了答案當前這一位 \(j\)\(0\),就要修改一下 \(Move\)

Move[i] &= ~a[i]>>j

模擬一下就知道是什麼原理了。

然後就省去了列舉的步驟,直接取lowbit。


#include<bits/stdc++.h>
using namespace std;
 
template<typename T>
inline void Read(T &n){
    char ch; bool flag=false;
    while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
    for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
    if(flag)n=-n;
}
 
typedef long long ll;
 
enum{
    MAXN = 100005
};
 
int n, w;
ll a[MAXN], ans, Move[MAXN], tmp[MAXN];
 
inline char check(){
    static ll cnt[61];
    for(int i=0; i<=w; i++) cnt[i] = 0;
    for(int i=1; i<=n; i++) cnt[__builtin_ffs(Move[i])-1]++;
/*
    __builtin_ffs(x) 最小的1是第幾位 
*/
    for(ll i=0, req=1; i<=w; i++){
        if(req <= cnt[i]) return true;
        req = (req-cnt[i])<<1;
    }
    return false;
}
 
int main(){
    int T; Read(T);
    while(T--){
        Read(n); Read(w);
        for(int i=1; i<=n; i++) Read(a[i]), Move[i] = (1ll<<w+1)-1;
        ans = 0;
        for(int i=w-1; ~i; i--){
            copy(Move+1,Move+n+1,tmp+1);
            for(int j=1; j<=n; j++) Move[j] &= ~a[j]>>i;
            if(!check()) ans |= 1ll<<i, copy(tmp+1,tmp+n+1,Move+1);
        }
        cout<<ans<<'\n';
    }
    return 0;
}