zoukankan      html  css  js  c++  java
  • 【2020省选Day2T3】LOJ3304 「联合省选 2020 A」作业题

    题目链接

    前置知识:(1) 矩阵树定理。(2) 多项式四则运算(加、减、乘、求逆(牛顿迭代))。

    网上的介绍很多。这里就不细讲了。

    首先要把这个(gcd)给拆掉,才能用我们熟悉的“矩阵树定理”等一系列方法来解题。

    众所周知,(sum_{d|x}varphi(d)=x)。本题中,我们就可以利用这个(varphi)来拆掉(gcd)。具体来说,如果我们用(T)来表示枚举的一棵树,(e_1,e_2,dots ,e_{n-1})来表示(T)包含的边的编号,那么首先,答案可以表示为:

    [sum_{T}left(sum_{i=1}^{n-1}w_{e_i} ight) imesgcd(w_{e_1},w_{e_2},dots,w_{e_{n-1}}) ]

    我们把(gcd(w_{e_1},w_{e_2},dots,w_{e_{n-1}}))用上述的公式带入,得到:

    [=sum_{T}left(sum_{i=1}^{n-1}w_{e_i} ight) imesleft(sum_{d|gcd(w_{e_1},w_{e_2},dots,w_{e_{n-1}})}varphi(d) ight) ]

    然后就可以把这个(d),放到前面来,得到:

    [=sum_{d=1}^{W}varphi(d) imesleft(sum_{substack{T\d|w_{e_1},dots ,d|w_{e_{n-1}}}}sum_{i=1}^{n-1}w_{e_i} ight) ]

    其中,(W)表示最大的权值。后面括号里的部分,相当于【所有【(w_{e_i})(d)的倍数的边】组成的子图】的所有生成树的边权和。

    普通的矩阵树定理,可以用来求所有生成树的边权积之和。其中最为大家所熟知的应用,就是当所有边权都为(1)时,就相当于是求生成树的数量,也就是生成树计数问题。它的实现,就是求矩阵行列式,因为需要用到高斯消元,所以时间复杂度是(O(n^3))的。

    但是本题中,要求的是边权和,而不是“边权积之和”。一种朴素的想法是考虑每一条边的贡献,也就是求【包含这条边的生成树数量】。它又等于【原图的生成树数量】减去【原图去掉这条边后的生成树数量】。对每条边都求一次【原图去掉这条边后的生成树数量】,时间复杂度(O(mn^3))。因为外层还要枚举(d),所以总时间复杂度(O(Wcdot mn^3)),无法通过本题。

    求所有生成树的边权和,其实有更好的方法。我们把每条边的边权,看做一个多项式((1+w_{e_i}x))。其中,(x)不是任何具体的数,它只是一个符号,表示多项式的“一次项”。那么,一个生成树,它的边权之的“一次项”前的系数,就是这颗生成树的边权(这可以根据多项式乘法的定义来理解)。

    如此以来,求所有生成树的边权和的问题,当我们把边权换成这样一个多项式后,就转化为了求所有生成树的边权积之和的问题。只不过,现在新的边权,不再是一个数,而是一个多项式。所以我们只需要定义出多项式的四则运算,就可以直接用矩阵树定理求解了。另外,我们只关心多项式的“一次项”系数,所以更高的项可以舍去。换句话说,所有的多项式运算,可以在(mod x^2)意义下进行。那么把多项式定义为一个( exttt{pair})就可以了。

    多项式的加、减、乘运算都比较简单。不了解的可以看如下代码:

    typedef pair<int,int> pii;
    #define fi first
    #define se second
    //为了便于理解,这里就不取模了
    pii operator+(const pii& a,const pii& b){
    	return mk(a.fi+b.fi,a.se+b.se);
    }
    pii operator-(const pii& a,const pii& b){
    	return mk(a.fi-b.fi,a.se-b.se);
    }
    pii operator*(const pii& a,const pii& b){
    	return mk(a.fi*b.fi,a.se*b.fi+b.se*a.fi);
    }
    

    多项式的除法,因为在(mod x^2)意义下进行,所以只需要求出多项式(mod x^2)意义下的逆即可。用经典的倍增法。设原多项式为(A(x)),要求的、它的逆为(F(x)=A^{-1}(x)pmod{x^n})。如果已经求出了(F_0(x)=A^{-1}(x)pmod{x^{lceilfrac{n}{2} ceil}}),那么:

    [F(x)=2F_0(x)-F_0^2(x)A(x)pmod{x^{n}} ]

    对本题来说,我们就不需要递归了。先求出常数项,也就是求逆元。然后把常数项作为(F_0(x))带入上式即可。具体的代码:

    pii inv(const pii& a){
    	int t=pow_mod(a.fi,MOD-2);
    	return mk(2*t,0)-mk(t,0)*mk(t,0)*a;
    }
    pii operator/(const pii& a,const pii& b){
    	return a*inv(b);
    }
    

    还有一个小细节是,高斯消元求行列式的时候,如果当前行的(要作为被除数)的这个多项式,常数项为(0),上述的求逆的方法就不管用了,因为(0)没有逆元。这种情况下,我们首先考虑,在它下面,找一个常数项不为(0)的行,与它交换(根据行列式的性质,两行交换后行列式要变号,也就是(res:=-res))。如果它下面所有行常数项都为(0),那对这一列,我们就不需要管常数项了,直接拿一次项消就行(就把每一项都看成普通的数而不是多项式就行)。

    至此,所有四则运算都可以(O(1))实现,所以做一次高斯消元,求出行列式的复杂度就是(O(n^3))的。外层还要枚举(d),所以总时间复杂度(O(Wn^3))。这个复杂度仍然无法通过本题,我们还需要再对(W)这部分做一些优化。

    发现对于一个(d)。如果“是它的倍数”的边,数量小于(n-1)条,那必不可能有任何生成树。特判这一情况后,我们惊喜地发现,复杂度降为:(O(frac{sumsigma_0(w_{e_i})}{n-1}cdot n^3)=O(n^2sumsigma_0(w_{e_i})))。其中(sigma_0(x))表示(x)的约数数量。这个复杂度很好理解,因为每个(d),要作为约数出现(n-1)次才被计算,所以被计算到的(d)最多只有(frac{sumsigma_0(w_{e_i})}{n-1})个。再看这个复杂度,一个小于等于(W)的数,约数个数是(O(sqrt{W}))级别的,又因为边数是(O(n^2))级别的,所以总复杂度就是(O(n^4sqrt{W}))。事实上,这个(sqrt{W})只是一个理论上限,在本题的数据范围下,打表发现,(w)的约数个数最多为(144)。足以通过本题。

    参考代码(在LOJ查看):

    //problem:LOJ3304
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    const int MOD=998244353;
    inline int mod1(int x){return x<MOD?x:x-MOD;}
    inline int mod2(int x){return x<0?x+MOD:x;}
    inline void add(int& x,int y){x=mod1(x+y);}
    inline void sub(int& x,int y){x=mod2(x-y);}
    inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
    
    pii operator+(const pii& lhs,const pii& rhs){
    	return mk(mod1(lhs.fi+rhs.fi),mod1(lhs.se+rhs.se));
    }
    pii operator-(const pii& lhs,const pii& rhs){
    	return mk(mod2(lhs.fi-rhs.fi),mod2(lhs.se-rhs.se));
    }
    pii operator*(const pii& lhs,const pii& rhs){
    	return mk((ll)lhs.fi*rhs.fi%MOD,((ll)lhs.se*rhs.fi+(ll)rhs.se*lhs.fi)%MOD);
    }
    pii inv(const pii& a){
    	//assert(a.fi!=0);
    	int t=pow_mod(a.fi,MOD-2);
    	return mk(2LL*t%MOD,0)-mk(t,0)*mk(t,0)*a;//牛顿迭代
    }
    pii operator/(const pii& lhs,const pii& rhs){
    	return lhs*inv(rhs);
    }
    pii operator-(const pii& rhs){
    	return mk(mod2(-rhs.fi),mod2(-rhs.se));
    }//负号
    pii& operator+=(pii& lhs,const pii& rhs){
    	lhs=lhs+rhs;
    	return lhs;
    }
    pii& operator-=(pii& lhs,const pii& rhs){
    	lhs=lhs-rhs;
    	return lhs;
    }
    pii& operator*=(pii& lhs,const pii& rhs){
    	lhs=lhs*rhs;
    	return lhs;
    }
    pii& operator/=(pii& lhs,const pii& rhs){
    	lhs=lhs/rhs;
    	return lhs;
    }
    
    const int MAXW=152501;
    int p[MAXW+5],cnt,phi[MAXW+5];
    bool v[MAXW+5];
    void sieve(int lim){
    	phi[1]=1;
    	for(int i=2;i<=lim;++i){
    		if(!v[i]){
    			phi[i]=i-1;
    			p[++cnt]=i;
    		}
    		for(int j=1;j<=cnt && p[j]*i<=lim;++j){
    			v[i*p[j]]=1;
    			if(i%p[j]==0){
    				phi[i*p[j]]=phi[i]*p[j];
    				break;
    			}
    			phi[i*p[j]]=phi[i]*phi[p[j]];
    		}
    	}
    }//线性筛,求phi
    
    const int MAXN=30;
    const int MAXM=MAXN*(MAXN-1)/2;
    int n,m;
    struct Edge{
    	int u,v,w;
    }e[MAXM+5];
    
    struct Matrix{
    	int n;
    	pii a[MAXN+5][MAXN+5];
    	Matrix(){
    		n=0;
    		memset(a,0,sizeof(a));
    	}
    };
    
    pii get_det2(Matrix A,int i){
    	//常数项全部为0
    	pii res=mk(1,0);
    	int line=0;
    	for(int j=i;j<=A.n;++j){
    		if(A.a[j][i].se!=0){
    			line=j;
    			break;
    		}
    	}
    	if(!line)return mk(0,0);
    	if(line!=i){
    		for(int j=1;j<=A.n;++j)swap(A.a[i][j],A.a[line][j]);
    		res=-res;
    	}
    	int _inv=pow_mod(A.a[i][i].se,MOD-2);
    	for(int j=i+1;j<=A.n;++j){
    		int t=(ll)A.a[j][i].se*_inv%MOD;
    		for(int k=1;k<=A.n;++k)
    			sub(A.a[j][k].se,(ll)A.a[i][k].se*t%MOD);
    	}
    	res*=A.a[i][i];
    	return res;
    }
    pii get_det(Matrix A){
    	pii res=mk(1,0);
    	for(int i=1;i<=A.n;++i){
    		int line=0;
    		for(int j=i;j<=A.n;++j){
    			if(A.a[j][i].fi!=0){
    				line=j;
    				break;
    			}
    		}
    		if(line==0){
    			res*=get_det2(A,i);
    			continue;
    		}
    		if(line!=i){
    			for(int j=1;j<=A.n;++j)swap(A.a[i][j],A.a[line][j]);
    			res=-res;
    		}
    		pii _inv=inv(A.a[i][i]);
    		for(int j=i+1;j<=A.n;++j){
    			pii t=A.a[j][i]*_inv;
    			for(int k=1;k<=A.n;++k)
    				A.a[j][k]-=A.a[i][k]*t;
    		}
    		res*=A.a[i][i];
    	}
    	return res;
    }
    
    int main() {
    	cin>>n>>m;
    	int max_w=0;
    	for(int i=1;i<=m;++i){
    		cin>>e[i].u>>e[i].v>>e[i].w;
    		max_w=max(max_w,e[i].w);
    	}
    	sieve(max_w);
    	int ans=0;
    	for(int i=1;i<=max_w;++i){
    		int cnt_e=0;
    		for(int j=1;j<=m;++j)if(e[j].w%i==0)cnt_e++;
    		if(cnt_e<n-1)continue;
    		
    		Matrix mat;
    		for(int j=1;j<=m;++j)if(e[j].w%i==0){
    			mat.a[e[j].u][e[j].u]+=mk(1,e[j].w);
    			mat.a[e[j].v][e[j].v]+=mk(1,e[j].w);
    			mat.a[e[j].u][e[j].v]-=mk(1,e[j].w);
    			mat.a[e[j].v][e[j].u]-=mk(1,e[j].w);
    		}
    		mat.n=n-1;
    		pii det=get_det(mat);
    		ans=((ll)ans+(ll)phi[i]*det.se)%MOD;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    
  • 相关阅读:
    D. Babaei and Birthday Cake--- Codeforces Round #343 (Div. 2)
    Vijos P1389婚礼上的小杉
    AIM Tech Round (Div. 2) C. Graph and String
    HDU 5627Clarke and MST
    bzoj 3332 旧试题
    codeforces 842C Ilya And The Tree
    codesforces 671D Roads in Yusland
    Travelling
    codeforces 606C Sorting Railway Cars
    codeforces 651C Watchmen
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13209932.html
Copyright © 2011-2022 走看看