G
N
I
D
A
O
L

【题解】对 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\) 的情况,只需实时维护最低次即可。

posted @ 2025-08-11 13:09  QWQcoding  阅读(17)  评论(0)    收藏  举报