有标号的DAG计数I
题目链接
COGS RIP
分析
感觉一下做不完,就四道题分开发博客(希望这不是个flag)。
问题很简单,就是求$n$个结点的带标号DAG的个数,要求时间复杂度为$O(n^2)$。
令$f[n]$为$n$个结点的带标号DAG的个数。
一个思路是枚举入度为$0$的结点的个数,套上一篇博客的容斥计数的公式,可以得到:
(f[n]=sum_{i=1}^{n}(-1)^{i-1} imes inom{n}{i} imes 2^{i(n-i)} imes f[n-i])
直接递推即可。
代码
#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int MAXN=5005;
const LL MOD=10007;
int n;
LL fac[MAXN],invf[MAXN],f[MAXN];
inline LL qpow(LL x,LL y){
LL ret=1,tt=x%MOD;
while(y){
if(y&1) ret=ret*tt%MOD;
tt=tt*tt%MOD;
y>>=1;
}
return ret;
}
inline LL binom(LL n,LL m){
if(n<0||m<0||n<m) return 0;
return fac[n]*invf[n-m]%MOD*invf[m]%MOD;
}
void init(){
fac[0]=1;
rin(i,1,n) fac[i]=fac[i-1]*i%MOD;
invf[n]=qpow(fac[n],MOD-2);
irin(i,n-1,0) invf[i]=invf[i+1]*(i+1)%MOD;
}
int main(){
n=read();
init();
f[0]=f[1]=1;
rin(i,2,n){
LL sgn=-1;
rin(j,1,i){
sgn=-sgn;
f[i]=(f[i]+sgn*binom(i,j)*qpow(2,j*(i-j))%MOD*f[i-j]%MOD+MOD)%MOD;
}
}
printf("%lld
",f[n]);
return 0;
}
有标号的DAG计数II
题目链接
分析
好吧,几个小时前立的flag现在就拔了。
考虑上一题的那个式子,看看能不能化成卷积的形式。
好吧还真可以,
(frac{f[n]}{n!sqrt{2}^{n^2}}=sum_{i=1}^{n}frac{(-1)^{i-1}}{i!sqrt{2}^{i^2}} imes frac{f[n-i]}{(n-i)!sqrt{2}^{(n-i)^2}})
化的过程比较麻烦(并不)就不写在这里了。
这里要用到二次剩余,提前算出来就好了。
这就是一个$F(x)=F(x) imes G(x)+1$的形式,多项式求逆即可。
时间复杂度为$O(n log n)$。
代码
#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int MAXN=100005;
const int NTT=1048576;
const LL MOD=998244353;
const LL G=3;
const LL IG=332748118;
const LL S=116195171;
const LL IS=557219762;
int N,n,m,len;
LL w[NTT+5],iw[NTT+5];
LL fac[MAXN],invf[MAXN];
LL g[MAXN];
LL rev[MAXN<<2],A[MAXN<<2],B[MAXN<<2];
inline LL qpow(LL x,LL y){
LL ret=1,tt=x%MOD;
while(y){
if(y&1) ret=ret*tt%MOD;
tt=tt*tt%MOD;
y>>=1;
}
return ret;
}
void ntt(LL *c,int dft){
rin(i,0,n-1)
if(i<rev[i])
std::swap(c[i],c[rev[i]]);
for(register int mid=1;mid<n;mid<<=1){
int r=(mid<<1),u=NTT/r;
for(register int l=0;l<n;l+=r){
int v=0;
for(register int i=0;i<mid;++i,v+=u){
LL x=c[l+i],y=c[l+mid+i]*(dft>0?w[v]:iw[v])%MOD;
c[l+i]=x+y<MOD?x+y:x+y-MOD;
c[l+mid+i]=x-y>=0?x-y:x-y+MOD;
}
}
}
if(dft<0){
LL invn=qpow(n,MOD-2);
rin(i,0,n-1) c[i]=c[i]*invn%MOD;
}
}
void getinv(int mdx){
if(mdx==1){
A[0]=qpow(g[0],MOD-2);
return;
}
getinv((mdx+1)>>1);
m=(mdx-1)+((((mdx+1)>>1)-1)<<1),len=0;
for(n=1;n<=m;n<<=1) ++len;
rin(i,1,n-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<(len-1)));
rin(i,0,n-1) B[i]=i<mdx?g[i]:0;
ntt(A,1);ntt(B,1);
rin(i,0,n-1) A[i]=(2*A[i]-B[i]*A[i]%MOD*A[i]%MOD+MOD)%MOD;
ntt(A,-1);
rin(i,mdx,n-1) A[i]=0;
}
void init(){
LL v=qpow(G,(MOD-1)/NTT),iv=qpow(IG,(MOD-1)/NTT);
w[0]=iw[0]=1;
rin(i,1,NTT-1) w[i]=w[i-1]*v%MOD,iw[i]=iw[i-1]*iv%MOD;
fac[0]=1;
rin(i,1,N) fac[i]=fac[i-1]*i%MOD;
invf[N]=qpow(fac[N],MOD-2);
irin(i,N-1,0) invf[i]=invf[i+1]*(i+1)%MOD;
}
int main(){
N=read();
init();
g[0]=0;
LL sgn=-1;
rin(i,1,N){
sgn=-sgn;
g[i]=(sgn*invf[i]*qpow(IS,1ll*i*i)%MOD+MOD)%MOD;
}
g[0]=1;
rin(i,1,N){
g[i]=(MOD-g[i])%MOD;
}
getinv(N+1);
printf("%lld
",A[N]*fac[N]%MOD*qpow(S,1ll*N*N)%MOD);
return 0;
}
有标号的DAG计数III
题目链接
分析
要求DAG弱连通。
我们先需要递推出DAGCNT1的$f$数组,然后从所有的DAG中减去不满足弱连通的。
类比城市规划那道题的思路,考虑枚举$1$号结点所在弱连通块的大小,可以得到状态转移方程:
(g[n]=f[n]-sum_{i=1}^{n-1}inom{n-1}{i-1} imes g[i] imes f[n-i])
其中$g[n]$表示$n$个结点的带标号弱连通DAG的个数。
时间复杂度为$O(n^2)$。
代码
#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int MAXN=5005;
const LL MOD=998244353;
int n;
LL fac[MAXN],invf[MAXN],pow2[MAXN*MAXN/4],f[MAXN],g[MAXN];
inline LL qpow(LL x,LL y){
LL ret=1,tt=x%MOD;
while(y){
if(y&1) ret=ret*tt%MOD;
tt=tt*tt%MOD;
y>>=1;
}
return ret;
}
inline LL binom(LL n,LL m){
if(n<0||m<0||n<m) return 0;
return fac[n]*invf[n-m]%MOD*invf[m]%MOD;
}
void init(){
fac[0]=1;
rin(i,1,n) fac[i]=fac[i-1]*i%MOD;
invf[n]=qpow(fac[n],MOD-2);
irin(i,n-1,0) invf[i]=invf[i+1]*(i+1)%MOD;
int lim=(n/2)*(n/2+1);pow2[0]=1;
rin(i,1,lim) pow2[i]=(pow2[i-1]<<1)%MOD;
}
int main(){
n=read();
init();
f[0]=f[1]=1;
rin(i,2,n){
LL sgn=-1;
rin(j,1,i){
sgn=-sgn;
f[i]=(f[i]+sgn*binom(i,j)*pow2[j*(i-j)]%MOD*f[i-j]%MOD+MOD)%MOD;
}
}
g[0]=g[1]=1;
rin(i,2,n){
rin(j,1,i-1){
g[i]=(g[i]+binom(i-1,j-1)*g[j]%MOD*f[i-j])%MOD;
}
g[i]=(f[i]-g[i]+MOD)%MOD;
}
printf("%lld
",g[n]);
return 0;
}
有标号的DAG计数IV
题目链接
分析
上一个式子可以化成这样:
(frac{g[n]}{(n-1)!}=frac{f[n]}{(n-1)!}-sum_{i=1}^{n-1}frac{g[i]}{(i-1)!} imes frac{f[n-i]}{(n-i)!})
也就是这样的形式:
(G(x)=F(x)-G(x) imes H(x))
(G(x)=frac{F(x)}{H(x)+1})
多项式求逆就好了,时间复杂度为$O(n log n)$。
话说多项式的常数项是真玄学。
还有一个非常神仙的多项式求$ln$的做法,可以看这篇博客。
代码
#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int MAXN=100005;
const int NTT=1048576;
const LL MOD=998244353;
const LL G=3;
const LL IG=332748118;
const LL S=116195171;
const LL IS=557219762;
int N,n,m,len;
LL w[NTT+5],iw[NTT+5];
LL f[MAXN],g[MAXN],h[MAXN];
LL fac[MAXN],invf[MAXN];
int rev[MAXN<<2];
LL A[MAXN<<2],B[MAXN<<2];
inline LL qpow(LL x,LL y){
LL ret=1,tt=x%MOD;
while(y){
if(y&1) ret=ret*tt%MOD;
tt=tt*tt%MOD;
y>>=1;
}
return ret;
}
void ntt(LL *c,int dft){
rin(i,0,n-1)
if(i<rev[i])
std::swap(c[i],c[rev[i]]);
for(register int mid=1;mid<n;mid<<=1){
int r=(mid<<1),u=NTT/r;
for(register int l=0;l<n;l+=r){
int v=0;
for(register int i=0;i<mid;++i,v+=u){
LL x=c[l+i],y=c[l+mid+i]*(dft>0?w[v]:iw[v])%MOD;
c[l+i]=(x+y<MOD?x+y:x+y-MOD);
c[l+mid+i]=(x-y>=0?x-y:x-y+MOD);
}
}
}
if(dft<0){
LL invn=qpow(n,MOD-2);
rin(i,0,n-1) c[i]=c[i]*invn%MOD;
}
}
void getinv(LL *c,int mdx){
if(mdx==1){
A[0]=qpow(c[0],MOD-2);
return;
}
getinv(c,(mdx+1)>>1);
m=(mdx-1)+((((mdx+1)>>1)-1)<<1);
for(n=1,len=0;n<=m;n<<=1,++len);
rin(i,1,n-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<(len-1)));
rin(i,0,n-1) B[i]=i<mdx?c[i]:0;
ntt(A,1);ntt(B,1);
rin(i,0,n-1) A[i]=(2*A[i]-B[i]*A[i]%MOD*A[i]%MOD+MOD)%MOD;
ntt(A,-1);
rin(i,mdx,n-1) A[i]=0;
}
void init(){
fac[0]=1;
rin(i,1,N) fac[i]=fac[i-1]*i%MOD;
invf[N]=qpow(fac[N],MOD-2);
irin(i,N-1,0) invf[i]=invf[i+1]*(i+1)%MOD;
LL v=qpow(G,(MOD-1)/NTT),iv=qpow(IG,(MOD-1)/NTT);w[0]=iw[0]=1;
rin(i,1,NTT-1) w[i]=w[i-1]*v%MOD,iw[i]=iw[i-1]*iv%MOD;
}
int main(){
N=read();
init();
g[0]=0;
LL sgn=-1;
rin(i,1,N){
sgn=-sgn;
g[i]=(sgn*invf[i]*qpow(IS,1ll*i*i)%MOD+MOD)%MOD;
}
g[0]=1;
rin(i,1,N) g[i]=(MOD-g[i])%MOD;
getinv(g,N+1);
rin(i,1,N) A[i]=A[i]*fac[i]%MOD*qpow(S,1ll*i*i)%MOD,f[i]=A[i]*invf[i-1]%MOD,h[i]=A[i]*invf[i]%MOD;
f[0]=0;
memset(A,0,sizeof A);
h[0]=1;
getinv(h,N+1);
m=(N<<1);
for(n=1,len=0;n<=m;n<<=1,++len);
rin(i,1,n-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<(len-1)));
rin(i,0,n-1) B[i]=i<=N?f[i]:0;
ntt(A,1);ntt(B,1);
rin(i,0,n-1) A[i]=A[i]*B[i]%MOD;
ntt(A,-1);
printf("%lld
",A[N]*fac[N-1]%MOD);
return 0;
}