毛皮立方体
题目描述
有个 $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; }