专栏文章
题解:P13275 [NOI 2025] 集合
P13275题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @miovbx4l
- 此快照首次捕获于
- 2025/12/03 01:45 3 个月前
- 此快照最后确认于
- 2025/12/03 01:45 3 个月前
赛时完全不知道怎么做,把三方暴力 dp 的 卡过了 带多测,然后最后一个小时才看出来有用的容斥,喜提没做完。
考虑这个特殊性质 B,大概就是让你把系数凑成 状物。但是完全不知道怎么凑啊?它看上去就像是一个选一个或者不选的组合意义。
考虑随机找个方向容斥。第一个想法是对着 容斥,但是我场上浪费了两个小时毫无进展。
记 为 中元素与和, 为 中元素 乘积。对于整数 ,记 为 ,即二进制下第 位。
考虑尝试对着 容斥。一个无敌的想法是考虑 (这里的减法视作集合减法,即 中存在元素 当且仅当 中存在 中不存在,此处即 表示 的 的集合, 同理)。
我们直接容斥 和 !枚举 ,容斥系数 ,表示对于 中的位,要求 所有数在这一位不含 , 至少存在一个这一位是 的;对于 中的位,要求 所有数在这一位不含 , 至少存在一个这一位是 的。
考虑对于“至少存在一个”继续容斥。枚举 表示容斥:对于 中的位, 这一位不含 (本来是 中的位 这一位得存在一个 ,枚举子集钦定不成立的容斥);对于 中的位, 这一位不含 。容斥系数 。这里的限制变成对于 的位, 这一位没 ; 的位, 这一位没 。
写出来相当于:
这里 表示二进制下 是 的子集。
假设你以及枚举了 ,考虑一个数 可以放在哪里(三种选择,放 ,放 ,都不放)。如果 ,则显然贡献是 ;如果上述不满足,且满足 或 ,则贡献是 ;否则贡献是 。
这里“如果上述不满足”的限制比较唐。在性质 B 中 ,也就是说 存在逆元。记 表示 , 表示 ,则贡献可以写为 。
预处理 可以简单高维后缀积,。然后暴力做是枚举 ,这里分析一下每一位是 的可能是:,所以复杂度是 。
考虑优化也很容易,如果先枚举 ,则 是独立的,复杂度 。
如果先枚举 ,然后高维前缀和预处理对于每一组 的 的贡献和,则复杂度 。考场上止步于此了,啊啊啊。
注意到人类智慧。我们的贡献只和 和 有关。如果枚举这两个,那唯一的问题就是如何处理 ?可以发现 的部分一定属于 。而这里的每一位都可以划给 和 ,所以竟然直接乘上一个 就好了!!!
记 。重写一下式子:
注意到 独立,我们直接做或卷积,所以做到了 。
但是这里 的定义有逆元,但是逆元不一定存在,只能通过性质 B。那咋办。
其实很好做,我们用 的形式存储一个数,其中 是模数。注意到我们只需要做加减乘除。加法的时候令 为两个里的小的那个,乘除的时候把 乘起来, 加起来就好了。
这种题代码都很好写。
CPP#include<bits/stdc++.h>
#define MOD 998244353
#define int long long
#define REP(i,a,n) for(int i=a;i<(int)(n);++i)
#define pb push_back
#define all(v) v.begin(),v.end()
#define pii pair<int,int>
#define cntbit(x) __builtin_popcount(x)
using namespace std;
int qpow(int x,int y){
int res=1;
while(y)res=y&1? res*x%MOD:res,x=x*x%MOD,y>>=1;
return res;
}
int ID;
struct number{
int x;
int c;
void operator =(int y){
if(y%MOD==0)x=1,c=1;
else x=y,c=0;
}
void operator *=(number a){
(x*=a.x)%=MOD;
(c+=a.c)%=MOD;
}
void operator *=(int a){
(x*=a)%=MOD;
}
void operator +=(number a){
int r=min(c,a.c);
x=((c==r)*x+(a.c==r)*a.x)%MOD,c=r;
}
void operator -=(number a){
int r=min(c,a.c);
x=((c==r)*x-(a.c==r)*a.x)%MOD,c=r;
}
};
int n;
int a[1100000];
number f[1100000],g[1100000];
void Main() {
cin>>n;
REP(i,0,(1<<n))cin>>a[i];
REP(i,0,(1<<n))f[i]=a[i]+1;
REP(i,0,n){
for(int j=(1<<n)-1;j>=0;--j)if(!((j>>i)&1)){
f[j]*=f[j|(1<<i)];
}
}
REP(i,0,(1<<n))f[i]*=qpow(-2,cntbit(i));
REP(j,0,n){
REP(i,0,(1<<n))if((i>>j)&1){
f[i]+=f[i^(1<<j)];
}
}
REP(i,0,(1<<n)){
f[i]*=f[i];
}
REP(j,0,n){
for(int i=(1<<n)-1;i>=0;--i)if((i>>j)&1){
f[i]-=f[i^(1<<j)];
}
}
REP(i,0,(1<<n)){
if(a[i]==MOD-1){
g[i]={a[i],-2};
}else g[i]=(a[i]*2+1)*qpow(a[i]+1,(MOD-2)*2)%MOD;
}
REP(i,0,n){
for(int j=(1<<n)-1;j>=0;--j)if(!((j>>i)&1)){
g[j]*=g[j|(1<<i)];
}
}
int ans=0;
REP(i,0,(1<<n)){
f[i]*=g[i];
if(f[i].c)continue;
int co=qpow((MOD+1)/2,cntbit(i))*f[i].x;
(ans+=co)%=MOD;
}
(ans+=MOD)%=MOD;
cout<<ans<<'\n';
}
signed main(){
int tc;
cin>>ID>>tc;
while(tc--)Main();
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...