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;
}