lgv 引理
学到了很多!
简述
对于有向无环图 \(G\),以及给定的 \(n\) 对起点,终点,设 \(S_p\) 为一组路径集合,满足起点 \(i\) 有一条连向终点 \(p_i\) 的路径,且路径无交点。
设矩阵 \(E\),其中 \(E_{i,j}\) 代表从起点 \(i\) 到终点 \(j\) 的路径条数。
那么,\(lgv\) 引理说的是:\(det(E)=\sum_{S_p}(-1)^{sign(p)}\prod\limits E_{i,p_i}\) 。
2023.5.24 T3 打怪升级
首先观察到区间必须不交,那么考虑画出 \(x-t\) 图像,等价于不相交路径计数,考虑 \(lgv\) 引理。
先不考虑不交,设 \(d_i=b_i-a_i,m=\frac{\sum\limits d_i}{2}\),那么我们要把 \(2m\) 步分到 \(n\) 个人上,如果每次可以选择同一个人,方案数为
但是要求不能选择同一个人,考虑容斥,枚举每个人被重复选了多少次,那么方案数为:
可以发现上面的式子等价于给每个位置分配一个多项式 \(F_{d_i}\) ,然后乘起来。
考虑套上 \(lgv\) ,设 \(E_{i,j}\) 为一个多项式 \(F_{b_j-a_i}\),那么所有不相交路径的多项式乘积和就是矩阵行列式。
经典套路,拉格朗日插值然后插出多项式系数。
一个新的 \(trick:\),\(O(n^2)\) 进行拉格朗日插值:按照定义式,每次除掉一个单项式,可以 \(O(n)\) 计算。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 4020;
const int mod = 1e9+7;
int Pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
int fac[N*32],ifac[N*32];
void init(int n)
{
fac[0]=1;
for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
ifac[n]=Pow(fac[n],mod-2);
for(int i=n-1;i>=0;i--)ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
}
int D[N][N];
int Gauss(int n)
{
int res=1;
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
if(D[j][i])
{
if(i==j)break;
for(int k=1;k<=n;k++)swap(D[i][k],D[j][k]);
res=mod-res;
break;
}
int inv=Pow(D[i][i],mod-2);
for(int j=i+1;j<=n;j++)
if(D[j][i])
{
int div=1ll*D[j][i]*inv%mod;
for(int k=1;k<=n;k++)
D[j][k]=(D[j][k]-1ll*D[i][k]*div%mod+mod)%mod;
}
}
for(int i=1;i<=n;i++)res=1ll*res*D[i][i]%mod;
return res;
}
int n;
struct segment
{
int l,r;
}S[N];
bool cmp(segment X,segment Y)
{
return X.l<Y.l;
}
int pw[N];
int ans[N];
int f[N],g[N];
int main()
{
freopen("upup.in","r",stdin);
freopen("upup.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++)scanf("%d",&S[i].l);
for(int i=1;i<=n;i++)scanf("%d",&S[i].r);
sort(S+1,S+n+1,cmp);
for(int i=1;i<=n;i++)
if(S[i].l>S[i].r)
{
cout<<0;
return 0;
}
for(int i=1;i<=n;i++)
if(S[i].r<=S[i-1].r)
{
cout<<0;
return 0;
}
int m=0;
for(int i=1;i<=n;i++)m+=S[i].r-S[i].l;
if(m&1)
{
cout<<0;
return 0;
}
init(m);
m/=2;
f[0]=1;
for(int i=0;i<=m;++i)
{
for(int j=i+1;j>=1;--j)
f[j]=(f[j-1]-1ll*i*f[j]%mod+mod)%mod;
f[0]=f[0]*(mod-i)%mod;
}
for(int x=0;x<=m;x++)
{
pw[0]=1;
for(int i=1;i<=200;i++)pw[i]=1ll*pw[i-1]*x%mod;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
D[i][j]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(S[j].r>=S[i].l)
{
int d=S[j].r-S[i].l;
for(int k=0;k*2<=d;k++)
D[i][j]=(D[i][j]+1ll*pw[k]*ifac[k]%mod*ifac[d-2*k]%mod)%mod;
}
int res=Gauss(n);
res=1ll*res*ifac[x]%mod;
res=1ll*res*ifac[m-x]%mod;
if((m-x)&1) res=mod-res;
g[m]=1;
ans[m]=(ans[m]+res)%mod;
for(int j=m-1;j>=0;j--)
{
g[j]=(f[j+1]+1ll*x*g[j+1]%mod)%mod;
ans[j]=(ans[j]+1ll*g[j]*res%mod)%mod;
}
}
int res=0;
for(int i=0;i<=m;i++)
{
int val=1ll*ans[i]*fac[m]%mod*fac[2*(m-i)]%mod*ifac[m-i]%mod;
if(i&1) res=(res-val+mod)%mod;
else res=(res+val)%mod;
}
res=1ll*res*Pow(Pow(2,mod-2),m)%mod;
cout<<res;
return 0;
}
[ABC216H] Random Robots
画出 \(x-t\) 图像,那么等价于不相交路径计数。
如果路径终点固定可以直接 \(lgv\),但是如果枚举终点又会炸裂。
考虑 \(lgv\) 计算了什么。
\(\sum (-1)^{sign(p)}\prod E_{i,p_i}\)
因为 \(n\) 非常小,我们考虑状压 \(dp\),按照值域,每次加入一个点,那么逆序对数量可以轻松计算,后面的方案数就是一个组合数。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 2010;
int p[N],n,m;
const int mod = 998244353;
inline int myplus(int a,int b){return (a+b>=mod?a+b-mod:a+b);}
int dp[(1<<10)+7],C[N][N],popcnt[(1<<10)+7];
inline int pw(int x){if(x&1)return mod-1;return 1;}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)scanf("%d",&p[i]);
dp[0]=1;
C[0][0]=1;
for(int i=1;i<N;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=myplus(C[i-1][j-1],C[i-1][j]);
}
for(int S=1;S<(1<<n);S++)popcnt[S]=popcnt[S>>1]+(S&1);
for(int q=0;q<=p[n]+m;q++)
for(int S=(1<<n)-1;S>=0;S--)
for(int i=1;i<=n;i++)
if((S>>(i-1))%2==0&&p[i]<=q&&q<=p[i]+m)
dp[S|(1<<(i-1))]=myplus(dp[S|(1<<(i-1))],1ll*dp[S]*C[m][q-p[i]]%mod*pw(popcnt[S>>(i-1)])%mod);
int inv2=(mod+1)/2,mul=1;
for(int i=1;i<=n*m;i++)mul=1ll*mul*inv2%mod;
cout<<1ll*dp[(1<<n)-1]*mul%mod;
return 0;
}
[AGC061F] Perfect Strings
非常厉害的题。
首先,建出一个 \(n\times m\) 的循环网格,把 \(0\) 看成向右走, \(1\) 向上走,那么等价于从 \((0,0)\) 走出一条路径回到 \((0,0)\) 且除了终点没有经过重复点的路径条数。
考虑所有走出方格外和走进方格内的步,一定是若干行提供了左插头,右插头,若干列提供了上插头,下插头,然后把左下插头和右上插头依次匹配,这就很 \(lgv\) 。
但是,我们要求是一个大环,不能有多个小环,如果有 \(i\) 个行插头,\(j\) 个列插头,那么要求就是 \(\gcd(i,j)=1\) 。
但是显然,我们不能枚举有插头的行列,考虑用多项式占位。
对于第 \(i\) 行,如果其放了插头,就让其贡献系数乘上 \(x\),否则就是贡献系数,列类似。
那么最终多项式里 \([x^iy^j]\) 就是 \(i\) 个行插头,\(j\) 个列插头的方案数。
同时,我们需要增添从每一行的左插头到右插头,权值为 \(1\)(没有x) 的边,代表这条边不选也可以走出一条路径。
那么再跑 \(lgv\) 就可以得到不相交路径数的带符号和。
通过分析,我们还可以发现,对于固定的 \(i,j\),逆序对个数就是 \(nm-ij\) ,因此可以求出不相交路径个数。
多项式可以用二维拉格朗日插值求出来。
但是,我们还要求路径,经过 \((0,0)\)。
但是,事实上每个点都是等价的,因此算出所有路径长度和除以 \(nm\) 就是一个点的答案,同时在 \(i,j\) 固定的时候环长也是确定的,因此都可以求出来。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 88;
const int mod = 998244353;
inline int plu(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
int Pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=mul(res,a);
a=mul(a,a);
b>>=1;
}
return res;
}
int binom[N][N];
void init(int n)
{
binom[0][0]=1;
for(int i=1;i<=n;i++)
{
binom[i][0]=1;
for(int j=1;j<=n;j++)
binom[i][j]=plu(binom[i-1][j-1],binom[i-1][j]);
}
}
int D[N][N];
int Gauss(int n)
{
int res=1;
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
if(D[j][i])
{
if(i==j)break;
for(int k=1;k<=n;k++)swap(D[i][k],D[j][k]);
res=mod-res;
break;
}
int inv=Pow(D[i][i],mod-2);
for(int j=i+1;j<=n;j++)
if(D[j][i])
{
int div=1ll*D[j][i]*inv%mod;
for(int k=1;k<=n;k++)
D[j][k]=dec(D[j][k],mul(D[i][k],div));
}
}
for(int i=1;i<=n;i++)res=mul(res,D[i][i]);
return res;
}
int n,m;
int solve(int x,int y)
{
for(int i=1;i<=n+m;i++)
for(int j=1;j<=n+m;j++)
D[i][j]=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
D[i][j]=mul(binom[j-1+i-1][j-1],x);
for(int j=1;j<=i;j++)
D[i][j+m]=mul(binom[m-1+i-j][i-j],x);
D[i][i+m]=plu(D[i][i+m],1);
}
for(int i=1;i<=m;i++)
{
for(int j=i;j<=m;j++)
D[i+n][j]=mul(binom[n-1+j-i][j-i],y);
D[i+n][i]=plu(D[i+n][i],1);
for(int j=1;j<=n;j++)
D[i+n][j+m]=mul(binom[m-i+n-j][m-i],y);
}
return Gauss(n+m);
}
int W[N][N];
int F[N][N],G[N][N],Ans[N][N];
int pw(int x)
{
if(x&1) return mod-1;
return 1;
}
int gcd(int a,int b)
{
if(!b) return a;
return gcd(b,a%b);
}
int main()
{
cin>>n>>m;
init(n+m);
for(int x=0;x<=n;x++)
for(int y=0;y<=m;y++)
W[x][y]=solve(x,y);
for(int i=0;i<=n;i++)
{
F[i][0]=1;
int coef=1;
for(int j=0;j<=n;j++)
if(i!=j)
{
for(int k=n;k>=0;k--)
{
F[i][k]=mul(F[i][k],mod-j);
if(k) F[i][k]=plu(F[i][k],F[i][k-1]);
}
coef=mul(coef,Pow(dec(i,j),mod-2))%mod;
}
for(int k=0;k<=n;k++)
F[i][k]=mul(F[i][k],coef);
}
for(int i=0;i<=m;i++)
{
G[i][0]=1;
int coef=1;
for(int j=0;j<=m;j++)
if(i!=j)
{
for(int k=m;k>=0;k--)
{
G[i][k]=mul(G[i][k],mod-j);
if(k) G[i][k]=plu(G[i][k],G[i][k-1]);
}
coef=mul(coef,Pow(dec(i,j),mod-2))%mod;
}
for(int k=0;k<=m;k++)
G[i][k]=mul(G[i][k],coef);
}
for(int x=0;x<=n;x++)
for(int y=0;y<=m;y++)
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
Ans[i][j]=plu(Ans[i][j],mul(W[x][y],mul(F[x][i],G[y][j])));
int res=0;
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
if(gcd(i,j)==1)
{
int coef=pw(n*m-i*j);
res=plu(res,mul(coef,mul(Ans[i][j],i*m+j*n)));
}
res=mul(res,Pow(n*m,mod-2));
cout<<res;
return 0;
}

浙公网安备 33010602011771号