CF1601A Array Elimination 题解

发布于 18 天前  54 次阅读


题意

题目链接

有一个长度为 $n$ 的序列 $a_1,a_2,\dots,a_n$,每次操作选择 $k$ 个数,将这 $k$ 个数减去他们的与(二进制运算中的与)的和。求哪些 $k$ 可以在有限次操作内使所有数变成 $0$。

解析

蛮有趣的。把所有数化成二进制后,你考虑每一位。如果共有 $x$ 个数在某一位上为 $1$,那么考虑如果指定了一个 $k$,那么这一位显然每次只能减少 $k$ 个 $1$ 或 $0$ 个 $1$。换言之,如果想要让这一位上全部变为 $0$,需要满足 $k|x$。

发现在这个过程中,每一位是独立的,因为可行的 $k$ 为每一位的 $x$ 的因子,即我们只需要求出每一位上为 $1$ 的数的个数,再将这些个数求一个 $\gcd$,这个求得的 $\gcd$ 的所有因子即为可行的 $k$。

特别的,如果所有数均为 $0$,答案显然可以为 $1\sim n$ 的所有数。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T,n,a[N],t[50];
inline int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
vector<int> ans;
inline void print(int x){//求答案
    ans.clear();
    for(int i=1;i<=sqrt(x);i++){
        if(x%i==0){
            ans.push_back(i);
            if(i*i!=x) ans.push_back(x/i);
        }
    }
    sort(ans.begin(),ans.end());
    for(auto x:ans) printf("%d ",x);
    printf("\n");
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);for(int i=0;i<=30;i++) t[i]=0;
        for(int i=1,x;i<=n;i++){
            scanf("%d",&x);
            for(int j=0;j<=30;j++){
                if(x&(1<<j)) t[j]++;//统计个数
            }
        }
        int g=0;
        for(int j=0;j<=30;j++) g=gcd(g,t[j]);//求 gcd
        if(g==0){for(int i=1;i<=n;i++) printf("%d ",i);printf("\n");}//特判全部为 0
        else print(g);
    }
}

月流华 岁遗沙 万古吴钩出玉匣