Link
题意
给定 2n 个点,编号为 1∼2n,两个点 x,y 之间有一条有向边当且仅当 y or x=x,y xor x=2k(k∈[0,n))。令 fx,y 为 x 点到 y 点的不同路径数,求:
i=1∑2nj=1∑2nfi,j(i=j)
答案对 998244353 取模。
分析
把编号的二进制表示看成集合,那么对于一条边 x→y,y 是 x 仅少了一个元素的子集。
显然点 2n 是没用的,因为没有点 0。对于任意 y or x=x,从 x 走到 y 相当于去掉 popcount(y xor x) 个元素,有 f(x,y)=popcount(y xor x)! 种路径,其他情况 f(x,y)=0。
但这样计算仍然是 O(22n) 的,考虑换个计算方式,把路径数相同的点对拿出来算,有
i=1∑2nj=1∑2nfi,j(i=j)
=y or x=x∑popcount(y xor x)!
=i=1∑ni!∑[popcount(y xor x)=i]
∑[popcount(y xor x)=i] 相当于 y 与 x 有 i 位不同的方案数,选出他们不同的位有 Cni 种方案,相同的位有 2n−i−1 种方案(y 不能为 0),所以:
i=1∑ni!∑[popcount(y xor x)=i]
=i=1∑ni!Cni(2n−i−1)
=i=1∑n(n−i)!n!(2n−i−1)
=n!i=0∑n−1i!2i−1
后面那个和式显然是可以预处理前缀和的,于是这道题就解决了。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
long long x=0,f=1;char ch=getchar();
while(!isdigit(ch))
{if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void write(long long x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=1e7+10;
const ll mod=998244353;
int n;
int fac[N],inv[N],m2[N],sum[N];
ll qmi(ll a,ll b){
ll ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;b>>=1;
}
return ans;
}
int main(){
fac[0]=inv[0]=m2[0]=1;
for(int i=1;i<=N-2;i++)fac[i]=1ll*fac[i-1]*i%mod;
inv[N-2]=qmi(fac[N-2],mod-2);
for(int i=N-3;i>=1;i--)inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=1;i<=N-2;i++)m2[i]=m2[i-1]*2%mod;
sum[0]=0;
for(int i=1;i<=N-2;i++){
sum[i]=(sum[i-1]+1ll*(m2[i]-1+mod)%mod*inv[i]%mod)%mod;
}
int T=read();
while(T--){
n=read();
write(1ll*sum[n-1]*fac[n]%mod);
puts("");
}
return 0;
}