【UOJ 310】【YBT2023寒假Day8 A】黎明前的巧克力 / 计数题(FWT)

黎明前的巧克力 / 计数题

题目链接:UOJ 310 / YBT2023寒假Day8 A

题目大意

给你一些数,然后两个人分别要选一个互相不交的集合(可以有一遍是空集,但是不能两边都空),然后问你有多少种方案使得两个集合的异或和相同。

思路

首先发现异或值相同那异或起来就是 0
那我们可以考虑这么一个东西,就是我们先求它们集合的并集,然后再把集合里面的元素分给它们,那对于大小为 x 的方案数就是 2x

然后我们考虑怎么弄这个东西,不难想到朴素的 DP。
fi,j 表示前 i 个元素选一些得到异或和为 j 的贡献。
然后对于每个数,你可以不选,也可以选(选了就是之前的基础上方案 2
然后就是 fi,j=fi1,j+2fi1,jai

然后这个东西显然可以用 FWT 优化,就是每次乘上一个数组 f:在 0 的位置是 1,在 ai 的位置是 2
但是这样每次都要乘还是不行啊。

接下来就是神奇的地方,我们发现每次乘上的东西很简单,我们考虑能不能从这里下手,然后我们会发现对于 FWT 之后的每一位,它要么是 1 要么是 3
(因为 0 下标肯定是全正贡献,然后 ai 的位置可正可负)
那我们如果直接把每个点的 f 数组加起来会怎样呢?
那众所周知会每个的加起来,那对于每一位,我们得到一个 fi
假设 1x3y 个,就有 x+y=n,x+3y=fi
也就是说我们就可以解方程得到 x,y,然后乘起来得到把这些 f 乘起来得到的值!
然后 IFWT 回去我们要的答案就是 f0 啦。

然后因为不能两个都是空集所以最后答案要减一。

代码

#include<cstdio> #define ll long long #define mo 998244353 using namespace std; const int N = 1e6 + 100; int n, a[N]; ll f[N << 1]; ll ksm(ll x, ll y) { ll re = 1; while (y) { if (y & 1) re = re * x % mo; x = x * x % mo; y >>= 1; } return re; } void FWT(ll *f, ll op, int n) { for (int mid = 1; mid < n; mid <<= 1) for (int R = mid << 1, j = 0; j < n; j += R) for (int k = 0; k < mid; k++) { int x = f[j | k], y = f[j | mid | k]; f[j | k] = (x + y) * op % mo; f[j | mid | k] = (x - y) * op % mo; } } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); f[0]++; f[a[i]] += 2; } int m = 1; while (m <= 1000000) m <<= 1; FWT(f, 1, m); for (int i = 0; i < m; i++) { int _1 = 0, _3 = 0; //_1+_3=n //-_1+3_3=f[i]; _3 = (f[i] + n) * ksm(4, mo - 2) % mo; _1 = n - _3; f[i] = ksm(mo - 1, _1) * ksm(3, _3) % mo; } FWT(f, (mo + 1) / 2, m); printf("%lld", (f[0] - 1 + mo) % mo); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/UOJ_310.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示