愚蠢的在线法官
题目描述
给定一棵树,每个点有权值\(v_i\),以及一个大小为\(k\)的数列\(A\)。
询问:
的行列式。
\(k,n\leq 5\times 10^{5}\)
解题思路
记\(a\to b\)表示\(a\)是\(b\)的祖先。
首先\(A\)中如果有两个数相同,那么矩阵中就有两行相同,那么行列式为0.
考虑一个特殊的情况,\(k=n\)。
考虑对于每个点求一个数组\(f\),满足 $$\forall u,v\in n, v_{LCA(u,v)}=\sum_{i=1}^nf[i][i\to LCA(u,v)]$$。
易得\(f[i]=v[i]-v[p[i]]\),其中\(p[i]\)为\(i\)的父亲。
我们发现\([i\to LCA(u,v)]\Leftrightarrow [i\to u][i\to v]\)
那么我们可以改写成 $$v_{LCA(u,v)}=\sum_{i=1}^nf[i][i\to u][i\to v]$$
容易发现这个式子可以写成矩阵乘法的形式,因此答案矩阵可以拆成两个矩阵:$$C_{i,j}=[j\to i]f[j],D_{i,j}=[i\to j] $$
由行列式的性质可得,方阵矩阵的行列式等于两个新方阵矩阵的行列式乘积。
如果我们事先把所有点按照深度从小到大排序,那么对于一个\(i\),所有\([j\to i]\)的\(j\)都在\(i\)的左边,因此 \(C,D\) 都是三角矩阵,所以行列式就是对角线的乘积.
如果\(k<n\),那么就不是方阵了,不能用上述的公式。
但是依然可以拆成一个\(k\times n\)的矩阵和一个\(n\times k\)的矩阵。
对于一个\(k\times n\)的矩阵\(A\),和\(n\times k\)的矩阵\(B\),由柯西-比内公式:
其中 \(A_S\)为取出\(A\)中位于\(S\)中的列形成的方阵,\(B_S\)同理。
考虑行列式的定义式:\(det(A)=\sum_{p}(-1)^{sign(p)}\prod_{i=1}^n A_{i,p_i}\)。
那么上式的组合意义就是,选出一个点集\(S\),称其为未匹配点,让\(A\)中的点和这些点匹配,满足\(A\)中点匹配的点必须是其祖先,求所有方案的\(S\)中的点的\(f\)的乘积和乘上方案数,再乘上各自排列的逆序对数的和。
对于一个合法匹配\(P\),若存在\(i,j\)使得交换\(P_i,P_j\)之后,\(P\)依然合法则称\(P\)为可交换的匹配。
引理1:所有可交换的匹配对答案的贡献和为0.
证明大概是找到一个双射,然后因为交换之后逆序对数奇偶性该变,但是答案不变,所以正好抵消。
因此我们只需要探讨所有不可交换的匹配。
引理2:对于一个选出的\(S\),若其存在某个\(A\)中的点,满足其祖先内至少有两个点属于\(S\),那么该\(S\)的所有匹配均可交换。
显然。
推论:若不满足上述条件,那么\(S\)的所有匹配均不可交换,我们称\(S\)为不可交换的。
引理3:对于一个存在匹配且不可交换的\(S\),其匹配唯一。
考虑对于每个\(A\)中的点,匹配其被选出的离他最近的祖先,若某两个点匹配的一样,则该点集是可交换的,矛盾。因此这是一个合法的匹配。
同时该匹配唯一,因为对于一个点,若其不匹配离他最近的祖先,那么必须要匹配一个深度更小的点,那么他原本的祖先的子树内必定有至少一个点无法被匹配,因此一定不合法。
因此对于选出的一个不可交换的\(S\),\(C\)矩阵和\(D\)矩阵都只能选出唯一一个匹配,因此逆序对数相同,就不需要考虑正负了。
那么现在就变成选出一个点集\(S\),满足\(S\)不可交换且存在匹配,求所有点的\(f\)乘积之和。
树形DP一下就好了。
时间复杂度\(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+7;
const int mod = 998244353;
struct edge
{
int y,next;
}e[2*N];
int Link[N],t=0;
void add(int x,int y)
{
e[++t].next=Link[x];
e[t].y=y;
Link[x]=t;
}
int f[N],g[N];
bool ins[N];
int n,k;
int v[N];
inline int plu(int a,int b){return (a+b>=mod?a+b-mod:a+b);}
void dfs(int x,int pre)
{
f[x]=1;
for(int i=Link[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==pre)continue;
dfs(y,x);
g[x]=plu(1ll*f[x]*g[y]%mod,1ll*g[x]*f[y]%mod);
f[x]=1ll*f[x]*f[y]%mod;
}
if(ins[x])
{
g[x]=f[x];
f[x]=1ll*f[x]*(v[x]-v[pre]+mod)%mod;
}
else f[x]=plu(f[x],1ll*g[x]*(v[x]-v[pre]+mod)%mod);
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)scanf("%d",&v[i]);
bool flag=0;
while(k--)
{
int x;
scanf("%d",&x);
if(ins[x]) flag=1;
ins[x]=1;
}
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d %d",&x,&y);
add(x,y);
add(y,x);
}
if(flag)
{
cout<<0;
return 0;
}
dfs(1,0);
cout<<f[1];
return 0;
}