毛皮立方体

题目描述

有个 $n \times n$ 的矩阵,要填入小于 $m$ 的自然数,有 $k$ 个格子已经填好了,要求横竖的和在模 $m$ 意义下都相等的方案数。

数据范围

$n,m \le 10^9;k \le 10^6$

题解

设有 $2n$ 个点分别表示行列,如果填入一个数的话就直接在点上加权,剩下空白的每个格子可以把它相对应的行列连接,形成二分图。考虑枚举最终每个行列的和,对于其中一个联通块,如果这个联通块是棵树,那它就是 $0/1$ 个解,如果是普通的联通块,考虑它的一个生成树,如果它有解,那剩下的边就可以乱填,因为加入一条边可以用树边来维持它合法。

考虑哪些解 $x$ 会让其合法,假设一个联通块有 $a$ 行 $b$ 列,这 $a$ 行和 $n-b$ 列的权值总和为 $A$ ,这 $b$ 列和 $n-a$ 行的权值总和为 $B$ ,那么 $(a-b) \times x = A-B (\mod m)$ ,这样就可以得到若干个同余方程,合并即可。

考虑怎么求出这些联通块及其信息,发现 $k$ 不大,即可以求补-二分图的联通块,用链表维护即可。

效率: $O(k)$

代码

#include <bits/stdc++.h>
using namespace std;
const int P=1e9+7,N=1e6+5;
int n,k,m,f[2][N],a[N*2],b[N*2],t,ans=1,L[2][N],R[2][N];
bool is[2][N],vs[N];
struct O{int u,v,w;}p[N];
struct Q{int o,x;};
queue<Q>q,h;vector<Q>e[2][N];
void add(int o,int u,int v,int w){
    e[o][u].push_back((Q){v,w});
}
int K(int x,int y){
    int z=1;
    for (;y;y>>=1,x=1ll*x*x%P)
        if (y&1) z=1ll*z*x%P;
    return z;
}
void del(int o,int x){
    L[o][R[o][x]]=L[o][x];R[o][L[o][x]]=R[o][x];
}
void bfs(int o,int x){
    del(o,x);q.push((Q){o,x});
    int z,w[2]={0,0},c[2]={0,0},v=0;Q u;
    while(!q.empty()){
        u=q.front();q.pop();c[u.o]++;
        (w[u.o]+=f[u.o][u.x])%=m;
        z=e[u.o][u.x].size();h.push(u);
        for (int i=0;i<z;i++)
            vs[e[u.o][u.x][i].o]=1;is[u.o][u.x]=1;
        for (int i=R[!u.o][0];i<=n;i=R[!u.o][i])
            if (!vs[i]) del(!u.o,i),q.push((Q){!u.o,i});
        for (int i=0;i<z;i++)
            vs[e[u.o][u.x][i].o]=0;
    }
    while(!h.empty()){
        u=h.front();h.pop();
        z=e[u.o][u.x].size();q.push(u);
        for (int i=0;i<z;i++)
            if (is[!u.o][e[u.o][u.x][i].o])
                (w[u.o]+=m-e[u.o][u.x][i].x)%=m,v--;
    }
    v=(1ll*c[0]*c[1]+(v/2)-(c[0]+c[1]-1))%(P-1);
    while(!q.empty())
        u=q.front(),q.pop(),is[u.o][u.x]=0;
    a[++t]=(c[0]-c[1]+m)%m;b[t]=(w[0]-w[1]+m)%m;
    ans=1ll*ans*K(m,v)%P;
}
int exgcd(int A,int B,int &x,int &y){
    if (!B){x=1;y=0;return A;}
    int v=exgcd(B,A%B,y,x);y-=A/B*x;return v;
}
int main(){
    cin>>n>>k>>m;
    for (int i=1;i<=k;i++)
        scanf("%d%d%d",&p[i].u,&p[i].v,&p[i].w);
    if (n>k) return printf("%d\n",
        K(m,(1ll*n*(n-2)-k+2)%(P-1))),0;
    for (int i=1;i<=k;i++)
        add(0,p[i].u,p[i].v,p[i].w),
        (f[0][p[i].u]+=p[i].w)%=m,
        add(1,p[i].v,p[i].u,p[i].w),
        (f[1][p[i].v]+=p[i].w)%=m;
    for (int i=1;i<=n;i++)
        L[0][i]=L[1][i]=i-1,R[0][i]=R[1][i]=i+1;
    R[0][0]=R[1][0]=1;L[0][n+1]=L[1][n+1]=n;
    for (;R[0][0]<=n;bfs(0,R[0][0]));
    for (;R[1][0]<=n;bfs(1,R[1][0]));
    for (int x,y,z,i=1;i<=t;i++){
        if (a[i]){
            z=exgcd(a[i],m,x,y);
            x=(x%(m/z)+m/z)%(m/z);
            if (b[i]%z) return puts("0"),0;
            y=b[i]/z;b[i]=m/z;a[i]=1ll*x*y%b[i];
        }
        else{
            if (b[i]) return puts("0"),0;
            a[i]=0;b[i]=1;
        }
    }
    for (int v,x,y,z,i=2;i<=t;i++){
        z=exgcd(b[1],b[i],x,y);
        x=(x%(b[i]/z)+(b[i]/z))%(b[i]/z);v=a[i]-a[1];
        if (v%z) return puts("0"),0;
        b[1]=b[1]/z*b[i];
        a[1]=((a[1]+1ll*b[1]/b[i]*v%b[1]*x%b[1])%b[1]+b[1])%b[1];
    }
    if (m<=a[1]) return puts("0"),0;
    ans=1ll*ans*((m-a[1]-1)/b[1]+1)%P;
    printf("%d\n",ans);return 0;
}

猜你喜欢

转载自www.cnblogs.com/xjqxjq/p/12003446.html
今日推荐