Loading

题解:AT_arc137_d [ARC137D] Prefix XORs

\(a\) 数组作 \(k\) 次前缀和后得到的 \(a_n=\sum_{i=0}\binom{k+i-1}{i}a_{n-i}\)

在贡献为异或情况下,若 \(\binom{k+i-1}{i}\mod2=1\),则对答案贡献 \(a_{n-i}\)

用 Lucas 定理可得:

\(\binom{n}{m}\mod2=1\) 当且仅当 \(n\operatorname{bitand}m=m\),即二进制下 \(m\)\(n\) 的子集。

于是我们写出一份暴力:

for(int i=1;i<=m;i++)
{
    for(int j=0;j<n;j++)
    {
        if(((i-1+j)&j)==j){
            ans[i]^=a[n-j];
        }
    }
}

我们把 \(i\) 改为从 0 开始,就可以得到一个 ((i+j)&j)==j,即二进制下 \(j\)\(i+j\) 的子集。

观察这个东西我们发现其实它相当于 !(i&j),即二进制下 \(i\)\(j\) 的交集为空。

证明:

假设二进制下 \(i\)\(j\) 的交集不为空,则这个交集的最低位在 \(i+j\) 中将为 0,但 \(j\) 此位为 1,假设不成立。

根据这个结论我们可以枚举 \(i\) 的二进制的补集,暴力统计答案。

时间复杂度不会证,但跑极限数据不会超时。

const int MAXN=1e6+10;
int n,m,all;//all为二进制下与n位数相同的,每一位都为1的数
int a[MAXN];
signed main(){
    scanf("%d%d",&n,&m);
    all=(1<<__lg(n)+1)-1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=0;i<m;i++)
    {
        int ans=a[n];//a[n]不会被枚举到
        int p=~i&all;
        for(int j=p;j;j=(j-1)&p)//枚举补集
        {
            if(j<n){
                ans^=a[n-j];
            }
        }
        printf("%d ",ans);
    }
    return 0;
}

然而我们发现这个暴力太慢了,很多点都跑到了 \(3s\) 以上。

发现这个暴力枚举补集其实可以 DP,直接枚举转移的二进制位。

时间复杂度 \(O(n\log{n})\)

const int MAXN=2e6+10;
int n,m,lg,all;
int dp[MAXN];//要开两倍数组
signed main(){
    scanf("%d%d",&n,&m);
    lg=__lg(n);
    all=(1<<lg+1)-1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&dp[n-i]);
    }
    for(int i=0;i<=lg;i++)//枚举转移的位
    {
        for(int j=1;j<=all;j++)
        {
            if(j&(1<<i)){
                dp[j]^=dp[j^(1<<i)];
            }
        }
    }
    for(int i=0;i<m;i++)
    {
        printf("%d ",dp[~i&all]);
    }
    return 0;
}
posted @ 2025-02-09 07:03  Mathew_Miao  阅读(3)  评论(0)    收藏  举报