【题解】对 NOI2025 D2T2 集合 除以0问题的讨论
这题在满分程序中需要用到 \(v\times 0^k\) 的 trick。
你是否曾想过 ifwt 时可能会出现 \(0^{-2}+0^{-1}-0^{-2}\) 的情况?
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read(){
ll x=0; bool f=1; char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=0; ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48); ch=getchar();
}
return f?x:-x;
}
inline void write(ll x){
if(x<0) x=-x,putchar('-');
if(x>9) write(x/10);
putchar('0'+x%10);
}
const ll MOD=998244353;
typedef pair<ll,ll> pll;
ll c,T,n;
inline ll qpow(ll a,ll b){
ll res=1;
while(b){
if(b&1) res=(res*a)%MOD;
b>>=1;
a=(a*a)%MOD;
}
return res;
}
inline ll inv(ll a){
return qpow(a,MOD-2);
}
// (k,v)
pll operator+(const pll &a,const pll &b){
if(a.first!=b.first) return min(a,b);
return make_pair(a.first,(a.second+b.second)%MOD);
}
pll operator-(const pll &a,const pll &b){
if(a.first!=b.first) return min(a,make_pair(b.first,MOD-b.second));
return make_pair(a.first,(a.second-b.second+MOD)%MOD);
}
pll operator*(const pll &a,const pll &b){
return make_pair(a.first+b.first,(a.second*b.second)%MOD);
}
void fwt(pll *f){
for(ll x=2;x<=(1<<n);x<<=1){
ll k=x>>1;
for(ll i=0;i<(1<<n);i+=x){
for(ll j=0;j<k;j++){
f[i+j+k]=f[i+j]+f[i+j+k];
}
}
}
}
void ifwt(pll *f){
for(ll x=2;x<=(1<<n);x<<=1){
ll k=x>>1;
for(ll i=0;i<(1<<n);i+=x){
for(ll j=0;j<k;j++){
f[i+j+k]=f[i+j+k]-f[i+j];
}
}
}
}
pll f[1<<21],g[1<<21];
ll a[1<<21];
ll pw2[33],ipw2[33];
const ll inv2=(MOD+1)/2;
void solve(){
n=read();
for(ll i=0;i<(1<<n);i++){
a[i]=read();
}
for(ll i=0;i<(1<<n);i++){
if(a[i]==MOD-1){
f[i]=make_pair(1,1);
g[i]=make_pair(-2,MOD-1);
}
else{
f[i]=make_pair(0,(a[i]+1)%MOD);
g[i]=make_pair(0,(2*a[i]+1)*inv((a[i]+1)*(a[i]+1)%MOD)%MOD);
}
}
for(ll x=2;x<=(1<<n);x<<=1){
ll k=x>>1;
for(ll i=0;i<(1<<n);i+=x){
for(ll j=0;j<k;j++){
f[i+j]=f[i+j]*f[i+j+k];
g[i+j]=g[i+j]*g[i+j+k];
}
}
}
for(ll i=0;i<(1<<n);i++){
ll cur=__builtin_popcount(i);
if(cur&1){
f[i].second=MOD-f[i].second;
}
f[i].second=f[i].second*pw2[cur]%MOD;
}
for(ll i=0;i<(1<<n);i++){
assert(f[i].first*2+g[i].first==0);
}
fwt(f);
for(ll i=0;i<(1<<n);i++){
f[i]=f[i]*f[i];
}
ifwt(f);
ll ans=0;
for(ll i=0;i<(1<<n);i++){
ll now=__builtin_popcount(i);
auto cur=f[i]*g[i];
assert(f[i].first+g[i].first==0);
// if(cur.first!=0) continue;
ans=(ans+cur.second*ipw2[now]%MOD)%MOD;
}
printf("%lld\n",ans);
}
int main(){
c=read(),T=read();
pw2[0]=ipw2[0]=1;
for(ll i=1;i<=25;i++){
pw2[i]=pw2[i-1]*2%MOD;
ipw2[i]=ipw2[i-1]*inv2%MOD;
}
while(T--){
solve();
}
return 0;
}
注意到:这份程序两个assert均正确,并且依旧获得了满分!
整个程序没有对 \(g\) 做任何加减法,只在对 \(f\) 做 fwt/ifwt 时出现。
注意到,第 \(84\) 行之前,对于每个 \(i\),\(g[i]\) 的 \(0\) 的次数 \(=\) \(f[i]\) 的 \(0\) 的次数 \(\times (-2)\)
在第 \(93\) 行之前 \(f\) 和 \(g\) 分别做 fmt ,此时一定有 若 \(S\subset T\),则 \(f_S\) 的次数 \(\geq\) \(f_T\) 的次数。(做乘法)
并且,此时依旧有 对于每个 \(i\),\(g[i]\) 的 \(0\) 的次数 \(=\) \(f[i]\) 的 \(0\) 的次数 \(\times (-2)\)
随后做 fwt 的时候,是做高维前缀和,而子集的次数不会更低,故还是维持原先 \(f_S\) 的最低次数,只改变系数。
因此在 ifwt 的时候,也不会受次数更高的子集的影响。从 \(101\) 行之后,\(f_S\) 的最低次数只由 \(f_S\) 本身决定,最终变为两倍。 做加减法也只会做和 \(f_S\) 最低次数相等的 \(f\) 。
也就是说,\(f_S\) 的最低次数最终一定等于 \(g_S\) 的次数,故 \(f_S\) 的更高次项一定不会有贡献。不用担心 \(f_S\) 最低次系数变为 \(0\) 的情况,只需实时维护最低次即可。

浙公网安备 33010602011771号