0%

[UNR#2] 黎明前的巧克力

今天一个集训队互测的前置题,补了感觉不错,所以写篇题解(

题目链接

题意

现在有 块巧克力与两个人,第 块巧克力权值为

定义一种选巧克力的方案是合法的,当且仅当:

  1. 两个人选的巧克力不重复;
  2. 两个人选的巧克力,权值的异或和相等;
  3. 两个人加在一起,至少选择了一个巧克力。

求合法方案数,对 取模。

题解

首先会发现,两个人选的巧克力异或和相等,就相当于所有被选中的巧克力,异或和是

那么对于每一个异或和为 的子集 ,他的贡献就是

所以我们只需要算出上述值,并减去 (空集)即可。

暴力 dp 是容易的,这里就不写了。

我们目前的主要目标是快速对所有异或和为 的子集计数。

不难发现,这个很像 FWT 所能干的事情。考虑使用异或卷积去做。

每一种数字 有两种选择:

  1. 不选,对异或和有 的贡献,对权值有 的贡献;
  2. 选,对异或和有 的贡献,对权值有 的贡献。

所以我们可以构造出多项式 ,其中

我们计算即可 即可,其中 为异或卷积。

所以我们可以在 的复杂度内计算出答案(还不如暴力dp。

仔细回想 FWT 的式子,对于一个多项式 而我们所有的多项式 ,都最多只有两项。 这就意味着,多项式 中,每一项的系数只能是


下面才是这道题的重点。

首先我们发现 具有分配律,即

那么就会有

这个式子是容易求的。我们设它是多项式

而我们想要求的其实是 ,其中 是按位乘。

根据前面的结论,我们又知道对于 的每一位 ,它可以被表示为 的形式,并且我们知道

而我们已经知道 了,那么就有 ,那我们就知道

这样,我们就可以把 求出来了。

就是答案,所以我们直接将得到的 做一个 即可。

时间复杂度

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int mod=998244353;
const ll inv2=(mod+1)/2;
const int L=1048576;
ll a[1048576];
ll p[1000001];
inline void FWT(int typ)
{
for(int len=1;len<L;len<<=1) for(int i=0;i<L;i++) if(!(i&len))
{
int num1=a[i],num2=a[i^len];
if(typ) a[i]=num1+num2,a[i^len]=num1-num2;
else a[i]=(num1+num2)*inv2%mod,a[i^len]=(num1-num2+mod)*inv2%mod;
}
}
int n;
int main()
{
n=read(),p[0]=1;
for(int i=1;i<=n;i++) a[0]++,a[read()]+=2,p[i]=p[i-1]*3%mod;
FWT(1);
for(int i=0;i<L;i++)
{
int A=(n+a[i])/4,B=(3*n-a[i])/4;
a[i]=p[A];if(B&1) a[i]=(mod-a[i])%mod;
}
FWT(0);
printf("%lld\n",(a[0]-1+mod)%mod);
return 0;
}