生成树求和(线代)

题意

给一张带权无向图,定义一颗生成树的权值为树上的边在三进制下不进位加法的和。求所有生成树的权值的和。

题解

第一时间应该想到矩阵树。
然后考虑去定义 A × B = A + B A \times B= A +B A×B=A+B(新定义)。

可以发现若将其表示成多项式形式 F ( x ) F(x) F(x),那么两个权值在三进制下加法的值,就是他们多项式乘积的项次 m o d mod mod 3 3 3

所以考虑拆位。为 i i i 则表示 f [ i ] = 1 f[i]=1 f[i]=1。所以 A × B = F A ( x ) × F B ( x ) ( m o d x 3 − 1 ) A \times B=F_A(x) \times F_B(x) \pmod{x^3-1} A×B=FA(x)×FB(x)(modx31) 中为 1 1 1 的项次。
我们注意到 F A ( x ) × F B ( x ) ( m o d x 3 − 1 ) F_A(x) \times F_B(x) \pmod{x^3-1} FA(x)×FB(x)(modx31) 其实就是 F A ( x ) F_A(x) FA(x) F B ( x ) F_B(x) FB(x) 在三次下的 F F T FFT FFT ,所以我们用复数 w 3 1 = ( − 1 2 , 3 2 ) w_3^1=(-\frac{1}{2},\frac{\sqrt{3}}{2}) w31=(21,23 ) 当做单位根。把点值求出来,用点值求行列式,最后再乘以转移逆矩阵即可求出系数 s 0 , s 1 , s 2 s_0,s_1,s_2 s0,s1,s2 ,答案即为 s 1 + 2 × s 2 s_1+2 \times s_2 s1+2×s2

题解

#include<bits/stdc++.h>
using namespace std;
const int N=105;
const int P=1e9+7;
const int G=917937628;
int n,m,st[N*N],ed[N*N],c[N*N],f[N][N][3],A;
struct Node{
	int a,b;
}z[N][N],s[3],g[N][N],ans[3],W[3];
inline int ksm(int a,int b){
	int s=1;while(b){
		if(b&1)s=1ll*s*a%P;
		a=1ll*a*a%P;b>>=1;
	}
	return s;
}
Node operator *(const Node x,const Node y){
	Node c;
	c.a=((1ll*x.a*y.a-1ll*x.b*y.b)%P+P)%P;
	c.b=(1ll*x.a*y.b+1ll*x.b*y.a)%P;
//	cout<<"CHneg    "<<x.a<<" "<<x.b<<" "<<y.a<<" "<<y.b<<" "<<c.a<<" "<<c.b<<endl;
	return c;
}
Node operator +(const Node x,const Node y){return (Node){(x.a+y.a)%P,(y.b+x.b)%P};}
Node operator -(const Node x,const Node y){return (Node){(x.a-y.a+P)%P,(x.b-y.b+P)%P};}
Node operator /(const Node x,const Node y){
	
	int inv=ksm((1ll*y.a*y.a+1ll*y.b*y.b)%P,P-2);
	Node c=(Node){1ll*y.a*inv%P,(P-1ll*y.b*inv%P)%P};
	return x*c;
}
inline Node det(){
	Node kp=(Node){1,0};
	for(int i=1;i<n;++i){
		for(int j=i+1;j<n;++j){
			if((z[j][i].a!=0||z[j][i].b!=0)){
				swap(z[i],z[j]);
			//	cout<<"zhuan "<<i<<" "<<j<<" "<<z[i][i].a<<" "<<z[i][i].b<<endl;
				kp=(Node){0,0}-kp;
				break;
			}
		}
	//	if(!z[i][i].a&&!z[i][i].b)return (Node){-1,0};
		for(int j=i+1;j<n;++j){
			Node bei=z[j][i]/z[i][i];
			for(int k=i;k<n;++k)z[j][k]=z[j][k]-z[i][k]*bei;
		}
	}
	for(int i=1;i<n;++i)kp=kp*z[i][i];
	return kp;
}
inline int solve(){
	for(int k=0;k<3;++k){
		for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)z[i][j]=(Node){0,0};
		for(int i=1;i<=n;++i){
			for(int j=1;j<=n;++j){
				for(int Z=0;Z<3;++Z)z[i][j]=z[i][j]+(Node){(f[i][j][Z]+P)%P,0}*W[Z*k%3];
			}
		}
		//for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)cout<<"JUzhen "<<i<<" "<<j<<" "<<z[i][j].a<<" "<<z[i][j].b<<endl;
		s[k]=det();
		if(s[k].a==-1)return 0;
	}
	ans[1]=W[0]*s[0]+W[2]*s[1]+W[1]*s[2];
	ans[2]=W[0]*s[0]+W[1]*s[1]+W[2]*s[2];
	return (ans[1].a+2ll*ans[2].a)%P*ksm(3,P-2)%P;
}
int main(){
/*	freopen("sum.in","r",stdin);
	freopen("sum.out","w",stdout);*/
	Node Wn=(Node){P-ksm(2,P-2),1ll*G*ksm(2,P-2)%P};
	W[0]=(Node){1,0};W[1]=Wn;W[2]=Wn*Wn;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)scanf("%d%d%d",&st[i],&ed[i],&c[i]);
	for(int k=0,kp=1;k<=12;++k,kp*=3){
		for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int Z=0;Z<3;++Z)f[i][j][Z]=0;
		for(int i=1;i<=m;++i){
			int a=st[i],b=ed[i],Z=(c[i]/kp)%3;
			f[a][a][Z]++;f[b][b][Z]++;
			f[a][b][Z]--;f[b][a][Z]--;
		}
		int su=solve();
		A=(A+1ll*kp*su)%P;
	}
	printf("%d\n",A);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值