zoukankan      html  css  js  c++  java
  • 【XSY3948】行列式(行列式,树形dp)

    题面

    行列式

    题解

    马神说:这可能是本场比赛最简单的一道题。

    黑人问号脸.jpg

    (这篇题解我很多地方写得很简略或很不严谨,所以如果有些地方看不懂请自己推一推)

    考虑构造矩阵 (B=(x)_{n imes n}),然后设矩阵 (C=A-B)

    那么矩阵 (C) 满足 (C_{p_i,i}=b_i-x)(C_{i,p_i}=c_i-x)(C_{i,i}=d_i-x),且其他地方的值都是 (0)

    然后我们要求的是 (det(A)=det(B+C)),考虑将其线性展开

    不会线性展开的可以利用下面的引理自己展开:

    引理:

    [egin{vmatrix} a_{1,1}&a_{1,2}&cdots&a_{1,n}\ vdots&vdots&&vdots\ a_{i-1,1}&a_{i-1,2}&cdots&a_{i-1,n}\ b_1+c_1&b_2+c_2&cdots&b_n+c_n\ a_{i+1,1}&a_{i+1,2}&cdots&a_{i+1,n}\ vdots&vdots&&vdots\ a_{n,1}&a_{n,2}&cdots&a_{n,n}\ end{vmatrix} =egin{vmatrix} a_{1,1}&a_{1,2}&cdots&a_{1,n}\ vdots&vdots&&vdots\ a_{i-1,1}&a_{i-1,2}&cdots&a_{i-1,n}\ b_1&b_2&cdots&b_n\ a_{i+1,1}&a_{i+1,2}&cdots&a_{i+1,n}\ vdots&vdots&&vdots\ a_{n,1}&a_{n,2}&cdots&a_{n,n}\ end{vmatrix} +egin{vmatrix} a_{1,1}&a_{1,2}&cdots&a_{1,n}\ vdots&vdots&&vdots\ a_{i-1,1}&a_{i-1,2}&cdots&a_{i-1,n}\ c_1&c_2&cdots&c_n\ a_{i+1,1}&a_{i+1,2}&cdots&a_{i+1,n}\ vdots&vdots&&vdots\ a_{n,1}&a_{n,2}&cdots&a_{n,n}\ end{vmatrix} ]

    证明:根据行列式的定义用乘法分配律即可简单证明。

    展开后,就能写成每行是 (B)(C) 对应行之一的 (2^n) 个矩阵的 (det) 的和。发现这 (2^n) 个矩阵中,那些选了大于一行 (B) 的矩阵肯定是没有贡献的,因为 (B) 是个全 (x) 矩阵,而我们又知道 “如果矩阵中有两行成比例,那么其行列式为 (0)”。

    所以要计算的矩阵中要么选了恰好一行 (B),要么没有选 (B)

    先考虑没有选 (B) 的情况,此时我们要计算的是 (det(C)=sumlimits_{pp}(-1)^{ au(pp_1,pp_2,cdots,pp_n)}C_{1,pp_1}C_{2,pp_2}cdots C_{n,pp_n})。(这里用 (pp) 只是为了和 (p) 区分开来)

    此时发现难以直接计算,就有一种比较神奇的做法:

    注意到条件 (1leq p_i<i),那么如果把 (p_i) 当做 (i) 的父亲的话,就会形成一棵树。

    由于行列式的定义中,我们每一行都要选一个位置乘起来,不妨设第 (i) 行选了 (C_{i,j})(只考虑 (C_{i,j} eq 0) 的情况)。

    那么我们不妨把选了 (C_{i,j}) 看做选了边 ((i,j)),发现选的边要么在树上,要么是树上某个点的自环。

    那么我们不妨给树上的边 ((i,p_i)) 加上边权 (c_i-x),边 ((p_i,i)) 加上边权 (b_i-x),同时再给树上的每一个点加上一条自环边,其边权为 (d_i-x)。(不妨仍称这个带有自环的图叫做 “树”)

    那这样选了 (C_{i,j}) 就相当于选了 ((i,j)) 的边权了。

    同时,行列式选完位置后,要保证每一行、每一列都恰好只选了一个位置。

    对应到树上,就代表树上选完边并对选的边统计每个点入度出度后(自环 ((u,u))(u) 的入度和出度各贡献 (1)),每个点的入度和出度恰好为 (1)

    这个时候会发现一个东西:如果我们选了一条边 ((i,j))(i eq j),即不考虑自环),那么我们一定也会选择边 ((j,i))。也就是说我们选边时一定是 ((i,p_i))((p_i,i)) 捆绑着同时选。

    证明比较简单,从树的叶子节点层一直往上推就行了。

    那这样就很好讨论了,直接树形dp即可。

    具体详见代码。

    有一个要注意的地方,就是如何确定每一种选择方案前面的系数((-1) 还是 (1))。

    假设我们选了 (k) 组捆绑的边,那么系数就是 ((-1)^k)

    原因(写得你可能看不懂):我们会把 ((i,p_i))((p_i,i)) 同时选,而这两个点在矩阵上是关于矩阵斜右向下的对角线对称的。不妨设我们选了一个点 (u=(x,y)),那么 (u) 的对称点 (u') 肯定也被选了。画画图可以得知,如果我们选的两个点 (u)(v)(v eq u'))构成逆序对,那么它们的对称点 (u')(v') 也会构成逆序对,此时 (-1) 就抵消了。所以只有 ((u,u')) 构成逆序对时才会有贡献,而 ((u,u')) 的数量就是我们选的捆绑的边的组数。

    那么既然系数和选的边的组数有关,那么系数就可以在树形dp时顺便处理了。

    而对于求选了恰好一行 (B) 的矩阵的行列式,假设选的是第 (i) 行,我们就把原来树上所有 (i) 的出边先去掉,再向所有点都连一条边权为 (x) 的边,然后也是要满足每个点的入度和出度恰好为 (1),要求不同选边方案的贡献的总和。

    这个我们同样也可以用树形dp来搞定。

    至于这时如何确定每一种选择方案前面的系数((-1) 还是 (1)),我们先称选的那条边权为 (x) 的边所在的环为 “(X) 环”。

    不妨设我们在 (X) 环上选了边 ((a_1,a_2),(a_2,a_3),cdots,(a_{k-1},a_k),(a_k,a_1)),在 (X) 环外选了边 ((b_1,c_1),(c_1,b_1),(b_2,c_2),(c_2,b_2),cdots,(b_l,c_l),(c_l,b_l)),那么我们要求的系数即为:

    [(-1)^{ au(b_1,c_1,b_2,c_2,cdots,b_l,c_l,a_1,a_2,cdots,a_{k-1},a_k)+ au(c_1,b_1,c_2,b_2,cdots,c_l,b_l,a_2,a_3,cdots,a_k,a_1)} ]

    我们只需讨论 ( au(b_1,c_1,b_2,c_2,cdots,b_l,c_l,a_1,a_2,cdots,a_{k-1},a_k)+ au(c_1,b_1,c_2,b_2,cdots,c_l,b_l,a_2,a_3,cdots,a_k,a_1)) 的奇偶性即可。

    记序列 (p_1=(b_1,c_1,b_2,c_2,cdots,b_l,c_l,a_1,a_2,cdots,a_{k-1},a_k)),将其分为左半部分 (pl_1=(b_1,c_1,b_2,c_2,cdots,b_l,c_l)) 和右半部分 (pr_1=(a_1,a_2,cdots,a_{k-1},a_k))

    同理得到 (p_2=(c_1,b_1,c_2,b_2,cdots,c_l,b_l,a_2,a_3,cdots,a_k,a_1))(pl_2=(c_1,b_1,c_2,b_2,cdots,c_l,b_l))(pr_2=(a_2,a_3,cdots,a_k,a_1))

    考虑 (pl_1) 中的数 (p_{1,i})(pr_1) 中的数 (p_{1,j}) 产生的逆序对集合 (P_1={(p_{1,i},p_{1,j})}),以及 (pl_2) 中的数 (p_{2,i})(pr_2) 中的数 (p_{2,j}) 产生的逆序对集合 (P_2={(p_{2,i},p_{2,j})}),不难发现这两个集合是完全相同的。因为 (pl_1) 中的数的集合和 (pl_2) 中的数的集合是完全相同的,而 (pr_1) 中的数的集合和 (pr_2) 中的数的集合也是完全相同的。

    所以 (P_1)(P_2) 对逆序对个数的贡献会因为只考虑奇偶性而被抵消掉。

    所以我们只需要考虑 (pl_1,pr_1,pl_2,pr_2) 它们各自的逆序对个数之和的奇偶性即可。

    (pl_1)(pl_2) 的贡献直接按我们第一种情况(即求 (det(C)) 的情况)处理即可,而对于求 (ig( au(pr_1)+ au(pr_2)ig) mod 2)

    [egin{aligned} au(pr_1)+ au(pr_2) =& au(a_1,a_2,cdots,a_{k-1},a_k)+ au(a_2,a_3,cdots,a_k,a_1)\ =& ext{$a_1\,$和$\,(a_2,cdots,a_{k-1},a_k)\,$的逆序对个数}+ ext{$(a_2,cdots,a_{k-1},a_k)\,$和$\,a_1\,$的逆序对个数}+\ &2 au(a_2,cdots,a_{k-1},a_k)\ equiv& ext{$a_1\,$和$\,(a_2,cdots,a_{k-1},a_k)\,$的逆序对个数}+ ext{$(a_2,cdots,a_{k-1},a_k)\,$和$\,a_1\,$的逆序对个数}\ equiv&k-1pmod 2 end{aligned} ]

    那么也可以在树形dp时顺便处理了。

    代码如下:

    #include<bits/stdc++.h>
    
    #define N 1000010
    
    using namespace std;
    
    namespace modular
    {
    	const int mod=1000000007;
    	inline int add(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;}
    }using namespace modular;
    
    inline int read()
    {
    	int x=0,f=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9')
    	{
    		if(ch=='-') f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9')
    	{
    		x=(x<<1)+(x<<3)+(ch^'0');
    		ch=getchar();
    	}
    	return x*f;
    }
    
    int n,x,d[N];
    int cnt,head[N],nxt[N<<1],to[N<<1],bb[N<<1],cc[N<<1];
    int f[N][6],g[6];
    //不妨设我们选的边权长度为x的边为(u,v),那么下面的“X路径”是指树上的路径v->u(即X环去掉(u,v)的那部分)
    //f[u][0]:已处理完u子树内的边,u子树内无X路径,u需要选与父亲相连的那条双向边(即两条单向边都选上)
    //f[u][1]:已处理完u子树内的边,u子树内无X路径,u不能选与父亲相连的那条双向边
    //f[u][2]:已处理完u子树内的边,u子树包含完整的X路径,u需要选与父亲相连的那条双向边
    //f[u][3]:已处理完u子树内的边,u子树包含完整的X路径,u不能选与父亲相连的那条双向边
    //f[u][4]:已处理完u子树内的边,X路径的终点在u子树内,起点在u子树外
    //f[u][5]:已处理完u子树内的边,X路径的起点在u子树内,终点在u子树外
    
    void adde(int u,int v,int b,int c)
    {
    	to[++cnt]=v;
    	bb[cnt]=b,cc[cnt]=c;
    	nxt[cnt]=head[u];
    	head[u]=cnt;
    }
    
    void dfs(int u)
    {
    	f[u][0]=f[u][4]=f[u][5]=1;
    	f[u][1]=d[u];
    	for(int i=head[u];i;i=nxt[i])
    	{
    		int v=to[i],b=bb[i],c=cc[i];
    		dfs(v);
    		memset(g,0,sizeof(g));
    		g[0]=mul(f[u][0],f[v][1]);
    		g[1]=mul(f[u][1],f[v][1]);
    		g[1]=add(g[1],mul(f[u][0],dec(0,mul(mul(b,c),f[v][0]))));
    		g[2]=mul(f[u][2],f[v][1]);
    		g[2]=add(g[2],mul(f[u][0],f[v][3]));
    		g[3]=mul(f[u][3],f[v][1]);
    		g[3]=add(g[3],mul(f[u][1],f[v][3]));
    		g[3]=add(g[3],mul(f[u][0],dec(0,mul(mul(b,c),f[v][2]))));
    		g[3]=add(g[3],mul(f[u][2],dec(0,mul(mul(b,c),f[v][0]))));
    		g[3]=add(g[3],mul(f[u][5],dec(0,mul(mul(x,b),f[v][4]))));
    		g[3]=add(g[3],mul(f[u][4],dec(0,mul(mul(x,c),f[v][5]))));
    		g[4]=mul(f[u][4],f[v][1]);
    		g[4]=add(g[4],mul(f[u][0],dec(0,mul(b,f[v][4]))));
    		g[5]=mul(f[u][5],f[v][1]);
    		g[5]=add(g[5],mul(f[u][0],dec(0,mul(c,f[v][5]))));
    		memcpy(f[u],g,sizeof(f[u]));
    	}
    	f[u][3]=add(f[u][3],mul(x,f[u][0]));//X路径是自环
    }
    
    int main()
    {
    	n=read(),x=read();
    	for(int i=1;i<=n;i++) d[i]=dec(read(),x);
    	for(int i=2;i<=n;i++)
    	{
    		int p=read(),b=dec(read(),x),c=dec(read(),x);
    		adde(p,i,b,c);
    	}
    	dfs(1);
    	printf("%d
    ",add(f[1][1],f[1][3]));
    	return 0;
    }
    /*
    3 1 
    1 1 1
    1 2 3
    1 4 5
    */
    
  • 相关阅读:
    shell进行mysql统计
    java I/O总结
    Hbase源码分析:Hbase UI中Requests Per Second的具体含义
    ASP.NET Session State Overview
    What is an ISAPI Extension?
    innerxml and outerxml
    postman
    FileZilla文件下载的目录
    how to use webpart container in kentico
    Consider using EXISTS instead of IN
  • 原文地址:https://www.cnblogs.com/ez-lcw/p/14508522.html
Copyright © 2011-2022 走看看