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;
}
posted @ 2021-07-07 19:36  oisdoaiu  阅读(34)  评论(0编辑  收藏  举报