【NOI2017模拟6.20】树形图求和

题目

这里写图片描述

解法一

首先对于树形图的个数这样算:
a[i][i]表示i号点的出度,a[i][j]表示从i到j的边个数的相反数
去掉第n行第n列后剩下的矩阵的行列式即为树形图个数
考虑枚举每条边并计算这条边的影响。
就是说我们枚举一条边(x,y)然后a[x][x]–,a[x][y]++重新计算行列式
对于如下矩阵:

A=a1,1an,1a1,nan,n

若我们将第k列改成c[]

B=a1,1an,1a1,k1an,k1c1cna1,k+1an,k+1a1,nan,n

考虑如下方程:
ij=1nai,jxj=ci

那么有:
det(B)=det(A)×xk

上述方程中x为未知数, xi 的意义为:把某一列的数加到第k列得到c的系数。
我们可以用c表示出所有的x,当我们需要查询的时候就直接扫一下代入就好了

解法二

而我的解法是这样的:
定义二元组(a,b)表示方案数为a,每种方案的权值和为b。
那么乘法相当于两个二元组的组合,即:

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

相应的也可以推出除法(虽然并没有什么组合意义),加法,减法
而其零元为:(0,0)
其单位元为:(1,0)
然后就可以直接代入进去做高斯消元了

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<bitset>
#include<map>

#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)

using namespace std;

typedef long long LL;
typedef double db;

int get(){
    char ch;
    while(ch=getchar(),(ch<'0'||ch>'9')&&ch!='-');
    if (ch=='-'){
        int s=0;
        while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';
        return -s;
    }
    int s=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';
    return s;
}

const int N = 310;
const int mo = 1E+9+7;

LL add(LL x,LL y){return x+y>=mo?x+y-mo:x+y;}
LL quickmi(LL x,LL tim){
    LL ans=1;
    for(;tim;tim/=2,x=x*x%mo)
    if (tim%2)ans=ans*x%mo;
    return ans;
}
struct numb{
    LL  a,b;
    numb(const int a_=0,const int b_=0){a=a_;b=b_;}
};
numb operator *(numb a,numb b){return numb(a.a*b.a%mo,add(a.a*b.b%mo,a.b*b.a%mo));}
numb operator +(numb a,numb b){return numb(add(a.a,b.a),add(a.b,b.b));}
numb operator -(numb a,numb b){return numb((a.a+mo-b.a)%mo,(a.b+mo-b.b)%mo);}
numb operator /(numb a,numb b){
    LL tmp=quickmi(b.a,mo-2);
    LL a_=a.a*tmp%mo;
    return numb(a_,((a.b+mo-a_*b.b%mo)%mo)*tmp%mo);
}
numb operator *(numb a,LL x){return numb(a.a*x%mo,a.b*x%mo);}
numb operator /(numb a,LL x){
    LL tmp=quickmi(x,mo-2);
    return numb(a.a*tmp%mo,a.b*tmp%mo);
}

struct matrix{
    numb a[N][N];
    numb* operator [](int x){return a[x];}
}a;
int n,m;
numb d[N];
int fa[N];
numb ny[N];

int getfather(int x){
    if (fa[x]==x)return x;
    return fa[x]=getfather(fa[x]);
}

int main(){
    freopen("calc.in","r",stdin);
    freopen("calc.out","w",stdout);
    n=get();m=get();
    fo(i,1,n)fa[i]=i;
    fo(i,1,m){
        int x=get(),y=get(),v=get();
        int tx=getfather(x),ty=getfather(y);
        fa[tx]=ty;
        numb u=numb(1,v);
        a[x][y]=a[x][y]+u;
        //a[y][x]=a[y][x]+u;
        d[x]=d[x]+u;
        //d[y]=d[y]+u;
    }
    bool bz=1;
    int lst=getfather(1);
    fo(i,2,n){
        int u=getfather(i);
        if (u!=lst){bz=0;break;}
    }
    if (!bz){
        printf("0\n");
        fclose(stdin);
        fclose(stdout);
    }
    fo(x,1,n)
        fo(y,1,n)
        a[x][y]=a[x][y]*(mo-1);
    fo(x,1,n)a[x][x]=d[x];
    n--;
    fo(i,1,n){
        fo(j,1,i-1)
        if (a[i][j].a||a[i][j].b){
            numb tmp=a[i][j]*ny[j];
            fo(x,1,n)a[i][x]=a[i][x]-tmp*a[j][x];
        }
        ny[i]=numb(1,0)/a[i][i];
    }
    numb ans=numb(1,0);
    fo(i,1,n)ans=ans*a[i][i];
    printf("%lld\n",ans.b);
    fclose(stdin);
    fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值