CF1119H Triple/西行寺无余涅槃

Discription

现在有 n 个物品,同时有 k 种属性,每个属性有其价值 wi

每个物品的一种属性对应一个权值 ai,j[0,2m)

现在对于 [0,2m) 中的每个 x 求出

{k1,kn}[i=1nai,ki=x]i=1nwki

n105,m16,m+k20

Solution

“FWT 变换矩阵本来就是满秩的”

一种朴素的做法是求出每物品的每个属性所形成的集合幂级数暴力 FWT 并将点值乘起来再还原

看起来还原的一步是不可少的,那么尝试加速求点值的部分

直接求得的 [xi]FWT[1n] 的项数达到了 Θ(n2m) 级别,那么尝试对其分类,最直接的方式就是使用 ±1 来表示点值系数的正负

更具体地,如果一个 iFWT(j=1kxai,j) 中系数表示法下的 k 个位置向点值表示法下的第 i 位贡献为 ctrictrk( i[1,k] ctri{1,1}),使用一个长度为 k 的二进制数字表示 ctr 数组,其中 ctri=1 的位让 x 的第 i 为是 1

尝试对 [0,2m) 中的每个 i 维护长度为 2k 的数组 cnti,其中 cnti,j 表示对点值表示法下位置 i 贡献状态为 j 的物品的数量

那么如果求出了 2m×2kcnti,j,容易得到每个状态的 wi 对该点值处的贡献和,快速幂再乘积即可得到正确点值

问题转化为了求所有的 cnti,j,一种常用的手段是找等量关系来解方程,为了简化问题,下面的讨论都只考虑对 [xq]FWT(ans) 处的 cnt 来展开

尝试取出 {1,k} 的一个子集 T,设 bi=j=1kai,j,写出其集合幂级数 i=1nFWT(xib)=FWT(i=1nxbi)

这里的和式变换是因为 FWT 的过程中系数是统一的,也就是说FWT 是线性变换,类似于可以使用一次函数进行 Lagrange 插值

我们考察 [xq]FWT(i=1nxbi)[xT]FWT(i=02k1cntq,ixi),先看左边:

注意到 i=1npopcount(ai&q)=popcount((i=1nai)&q)

如果你不会证明

这个等量关系在 ai,q1 的时候按二进制位拆开考虑即可,因为位之间独立,n=2 时讨论是 q=ai 与否即可

n>2 的时候可以令 a1=a1a2,[i2]ai=ai+1,使用 n=2 的 RHS 把 LHS 两项缩成一项即可归到 nn1 的子问题


所以根据 FWT 的定义式子可以进行一些简单的和式变换:

[xq]FWT(i=1nxbi)=i=1njT(1)|ai,j&q|=s=02k1cntq,s(1)popcount(s&T)=[xT]FWT(i=02k1cntq,ixi)

第二行到第三行是发生了统计方式的转化:原来是逐物品枚举,同时将 T 包含的,给 q 点值有 1 贡献的 j 给予 1 贡献,而第三行不过是分类枚举,但是仍然强制在 T 集合内的 j 才能产生 1 贡献,因此根据实际含义找到了这样一组等量关系

事已至此,时间复杂度 Θ(2k(n+m2m))

Sample Code

const int N=1e5+10,MaxS=1<<16;
int n,m,k,w[10],tas[N][10],sw[100];
vector<int> c[MaxS];
signed main(){
    n=read(); m=read(); k=read();
    int U=1<<m;
    vector<int> ans; ans.resize(U);
    rep(i,1,k) w[i]=read();
    rep(i,1,n) rep(j,1,k) tas[i][j]=read();
    
    int S=1<<k;
    for(int i=0;i<S;++i){
        for(int j=1;j<=k;++j){
            if(i>>(j-1)&1) ckdel(sw[i],w[j]); 
            else ckadd(sw[i],w[j]);
        }
    }
    for(int i=0;i<U;++i) c[i].resize(S);
    for(int i=0;i<S;++i){
        vector<int> f; f.resize(U);
        for(int j=1;j<=n;++j){
            int xsum=0;
            for(int e=1;e<=k;++e) if(i>>(e-1)&1){
                xsum^=tas[j][e];
            }
            f[xsum]++;
        }
        FWT(f,U,1);
        for(int j=0;j<U;++j) c[j][i]=f[j];
    }
    for(int i=0;i<U;++i){
        FWT(c[i],S,-1);
        ans[i]=1;
        for(int j=0;j<S;++j) ckmul(ans[i],ksm(sw[j],c[i][j]));
    }
    FWT(ans,U,-1);
    rep(i,0,U-1) print(ans[i]);
    return 0;
}
posted @   没学完四大礼包不改名  阅读(149)  评论(5编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示