zoukankan      html  css  js  c++  java
  • 题解 P6624 【[省选联考 2020 A 卷] 作业题】

    传送门

    好题啊,真的好题啊!

    莫比乌斯反演+矩阵树定理(甚至还算加了一点点的生成函数的原理)


    【分析】

    根据题意,求对所有的树 (T) 的值:

    (displaystyle ans=sum_{T}val(T)=sum_{T}(sum_{i=1}^{n-1}w_{e_i})gcd_{i=1}^{n-1} w_{e_i})

    (e_i) 表示树 (T) 的边

    由欧拉反演得:(displaystyle n=sum_{dmid n}oldsymbol varphi(d))

    因此 (displaystyle gcd_{i=1}^{n-1} w_{e_i}=sum_{dmid gcd_{i=1}^{n-1}w_{e_i}}oldsymbol varphi(d)=sum_{forall i o dmid w_{e_i}}oldsymbol varphi(d))

    因此 (displaystyle ans=sum_T(sum_{i=1}^{n-1}w_{e_i})sum_{forall i o dmid w_{e_i}}oldsymbol varphi(d))

    调换枚举顺序得 (displaystyle ans=sum_{dgeq 1}oldsymbol varphi(d)sum_T[forall i o dmid w_{e_i}](sum_{i=1}^{n-1}w_{e_i}))

    于是对于每个 (w_{e_i}) ,我们将它塞进它各因数的 vector 中。之后,每个数 (d)vector 中,存着所有边权为它倍数的边

    现考虑给定一个 vector ,里面存着若干边,用这些边生成树的权值和为多少,即 (displaystyle f(d)=sum_{Tin v_d}(sum_{i=1}^{n-1}w_{e_i}))

    (ecause displaystyle sum_{i=1}^{n-1}w_{e_i}=sum_{i=1}^{n-1}w_{e_i}cdot 1)

    若令 (P_i(x)=1+w_{e_i}x)

    (displaystyle sum_{i=1}^{n-1}w_{e_i}cdot 1=sum_{c_1+c_2+c_3+cdots+c_{n-1}=1}prod_{i=1}^{n-1}P_i[c_i])

    后面这玩意儿本质上就是卷积,而且还是 (1) 次项的系数

    ( herefore displaystyle sum_{i=1}^{n-1}w_{e_i}=(prod_{i=1}^{n-1}P_i(x))[1])

    ( herefore displaystyle f(d)=sum_{Tin v_d}((prod_{i=1}^{n-1}P_i(x))[1])=(sum_{Tin v_d}prod_{i=1}^{n-1}P_i(x))[1])

    于是这就构成了矩阵树的形式。我们将 vector 中的边,构造成对应的一次函数,放进矩阵中,求一下行列式在模 (x^2) 意义下的值。再把一次项系数返回,就是 (f(d)) 的结果

    消元方法见下

    这样一来,(displaystyle ans=sum_{dgeq 1}oldsymbol varphi(d)f(d))

    如果每个 (d) 都求解,复杂度将达到 (O(mcdot n^3)) ,其中 (m) 为边的最大值。复杂度直接爆炸。

    考虑将 vector 中的边跑一下并查集。如果最后没有并成一个连通分量,那一定不会生成树,贡献为 (0) ,直接不跑高斯消元。

    这样的剪枝相当于额外增加一个 (O(mncdot alpha(n))) ,但感觉是能扣除一大部分高斯消元。试试能不能过(最后当然是过了)


    简单讲一下多项式怎么在模 (x^2) 意义下高斯消元。将 (P(x)=a+bx) 记为 ((a,b)) 则:

    ((a,b)pm(c,d)=(apm c,bpm d))

    ((a+bx)(c+dx)=ac+(ad+bc)x+bdx^2equiv ac+(ad+bc)xpmod {x^2})

    ((a,b)cdot (c,d)=(ac,ad+bc))

    ((a+bx)(c+dx)equiv 1pmod {x^2})

    (ac=1,ad+bc=0)

    (c=a^{-1},d=-(bc)cdot a^{-1}=-bc^2)

    因此 ((a,b)^{-1}=(a^{-1},-a^{-2}b))

    ((a,b)/(c,d)=(a,b)cdot (c,d)^{-1})

    这样一来就可以重载运算符了:

    typedef pair<int,int> pii;
    #define fi first
    #define se second
    inline pii operator ~ (const pii &p) { ll tmp=inv(p.fi); return pii(tmp,dis(0,tmp*tmp%MOD*p.se%MOD)); }
    //逆元
    inline pii operator - (const pii &p) { return pii( dis(0,p.fi) , dis(0,p.se) ); }
    inline pii operator + (const pii &a,const pii &b) { return pii( add(a.fi,b.fi) , add(a.se,b.se) ); }
    inline pii operator += (pii &a,const pii &b) { return a=a+b; }
    inline pii operator - (const pii &a,const pii &b) { return pii( dis(a.fi,b.fi) , dis(a.se,b.se) ); }
    inline pii operator -= (pii &a,const pii &b) { return a=a-b; }
    inline pii operator * (const pii &a,const pii &b) { return pii( (ll)a.fi*b.fi%MOD , ((ll)a.fi*b.se+(ll)a.se*b.fi)%MOD ); }
    inline pii operator *= (pii &a,const pii &b) { return a=a*b; }
    inline pii operator / (const pii &a,const pii &b) { return a*(~b); }
    inline pii operator /= (pii &a,const pii &b) { return a=a/b; }
    

    【代码】

    最后奉上本蒟蒻的代码

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef pair<int,int> pii;
    #define fi first
    #define se second
    const int MOD=998244353,MAXN=32,Lim=152501;
    inline ll fpow(ll a,ll x) { ll ans=1; for(;x;x>>=1,a=a*a%MOD) if(x&1) ans=ans*a%MOD; return ans; }
    //快速幂
    inline ll inv(ll a) { return fpow(a,MOD-2); }
    inline int add(int a,int b) { return (a+b>=MOD)?(a+b-MOD):(a+b); }
    inline int dis(int a,int b) { return (a-b<0)?(a-b+MOD):(a-b); }
    //求余意义下的逆元、加减法
    
    inline pii operator ~ (const pii &p) { ll tmp=inv(p.fi); return pii(tmp,dis(0,tmp*tmp%MOD*p.se%MOD)); }
    //逆元
    inline pii operator - (const pii &p) { return pii( dis(0,p.fi) , dis(0,p.se) ); }
    inline pii operator + (const pii &a,const pii &b) { return pii( add(a.fi,b.fi) , add(a.se,b.se) ); }
    inline pii operator += (pii &a,const pii &b) { return a=a+b; }
    inline pii operator - (const pii &a,const pii &b) { return pii( dis(a.fi,b.fi) , dis(a.se,b.se) ); }
    inline pii operator -= (pii &a,const pii &b) { return a=a-b; }
    inline pii operator * (const pii &a,const pii &b) { return pii( (ll)a.fi*b.fi%MOD , ((ll)a.fi*b.se+(ll)a.se*b.fi)%MOD ); }
    inline pii operator *= (pii &a,const pii &b) { return a=a*b; }
    inline pii operator / (const pii &a,const pii &b) { return a*(~b); }
    inline pii operator /= (pii &a,const pii &b) { return a=a/b; }
    //重载运算符
    
    struct Matrix{
        pii Num[MAXN][MAXN];
        inline pii det(int N){//求行列式
            pii res=pii(1,0);
            for(register int i=1;i<=N;++i){
                int pos=i;
                while(pos<=N&&Num[pos][i]==pii(0,0)) ++pos;
                if(pos>N) return pii(0,0);
                if(pos!=i) swap(Num[pos],Num[i]),res=-res;
                res*=Num[i][i];
                for(register int j=N;j>=i;j--) Num[i][j]/=Num[i][i];
    
                for(register int k=i+1;k<=N;++k)
                    for(register int j=N;j>=i;--j)
                        Num[k][j]-=Num[i][j]*Num[k][i];
            }
            return res;
        }
    }M;
    struct Edge{
        int u,v,w;
        Edge(int u_=0,int v_=0,int w_=0):u(u_),v(v_),w(w_) {}
    };
    int n,Pa[MAXN],CntSet;
    int find(int u) { return (u==Pa[u])?u:(Pa[u]=find(Pa[u])); }
    inline void merge(int u,int v){
        if(find(u)==find(v)) return ;
        Pa[find(u)]=find(v);
        --CntSet;
    }
    //并查集相关操作
    inline int calc(const vector<Edge> &v){
        //给定 vector ,求答案
        if(v.size()<n-1) return 0;
        //一定不成树
        iota(Pa+1,Pa+n+1,1); CntSet=n;
        memset(M.Num,0,sizeof(M.Num));
        //并查集和矩阵初始化
        for(const auto &e : v)
            merge(e.u,e.v),
            M.Num[e.u][e.v]-=pii(1,e.w),
            M.Num[e.v][e.u]-=pii(1,e.w),
            M.Num[e.u][e.u]+=pii(1,e.w),
            M.Num[e.v][e.v]+=pii(1,e.w);
        if(CntSet!=1) return 0;//无法生成树
        return M.det(n-1).se;
    }
    vector<Edge> e[Lim+1];
    ll fc[Lim+1],phi[Lim+1],prime[Lim+1],cntprime;
    inline void sieve(){
        //欧拉筛积性函数
        phi[1]=1;
        for(int i=2;i<=Lim;i++){
            if(!fc[i]) phi[i]=(fc[i]=prime[++cntprime]=i)-1;
            for(int j=1;j<=cntprime;j++)
                if(i*prime[j]>Lim) break;
                else if(fc[i]>prime[j]) fc[i*prime[j]]=prime[j],phi[i*prime[j]]=phi[i]*(prime[j]-1);
                else{
                    fc[i*prime[j]]=prime[j];
                    phi[i*prime[j]]=phi[i]*prime[j];
                    break;
                }
        }
    }
    inline void pre(){
        int m;
        cin>>n>>m;
        for(int i=1,u,v,w;i<=m;i++){
            cin>>u>>v>>w;
            for(int d=1;d*d<=w;d++) if(w%d==0) {
                e[d].push_back( Edge(u,v,w) );
                if(w/d!=d) e[w/d].push_back( Edge(u,v,w) );
            }
            //给因数打上标记
        }
    }
    int main(){
        ios::sync_with_stdio(0);
        cin.tie(0); cout.tie(0);
        pre();
        sieve();
        ll ans=0;
        for(int i=1;i<=Lim;i++) ans+=phi[i]*calc(e[i])%MOD;
        ans%=MOD;
        cout<<ans;
        cout.flush();
        return 0;
    }
    
  • 相关阅读:
    Android自定义之仿360Root大师水纹效果
    Android之TextView的Span样式源码剖析
    Android之TextView的样式类Span的使用详解
    随着ScrollView的滑动,渐渐的执行动画View
    仿微信主界面导航栏图标字体颜色的变化
    android自定义之 5.0 风格progressBar
    Android性能优化之内存篇
    Android性能优化之运算篇
    How to install Zabbix5.0 LTS version with Yum on the CentOS 7.8 system?
    How to install Zabbix4.0 LTS version with Yum on the Oracle Linux 7.3 system?
  • 原文地址:https://www.cnblogs.com/JustinRochester/p/14355638.html
Copyright © 2011-2022 走看看