题目描述
有个 n × n n \times n n×n 的矩阵,要填入小于 m m m 的自然数,有 k k k 个格子已经填好了,要求横竖的和在模 m m m 意义下都相等的方案数。
数据范围
n , m ≤ 1 0 9 ; k ≤ 1 0 6 n,m \le 10^9;k \le 10^6 n,m≤109;k≤106
题解
设有 2 n 2n 2n 个点分别表示行列,如果填入一个数的话就直接在点上加权,剩下空白的每个格子可以把它相对应的行列连接,形成二分图。考虑枚举最终每个行列的和,对于其中一个联通块,如果这个联通块是棵树,那它就是 0 / 1 0/1 0/1 个解,如果是普通的联通块,考虑它的一个生成树,如果它有解,那剩下的边就可以乱填,因为加入一条边可以用树边来维持它合法。
考虑哪些解 x x x 会让其合法,假设一个联通块有 a a a 行 b b b 列,这 a a a 行和 n − b n-b n−b 列的权值总和为 A A A ,这 b b b 列和 n − a n-a n−a 行的权值总和为 B B B ,那么 ( a − b ) × x = A − B ( m o d m ) (a-b) \times x = A-B (\mod m) (a−b)×x=A−B(modm) ,这样就可以得到若干个同余方程,合并即可。
考虑怎么求出这些联通块及其信息,发现 k k k 不大,即可以求补-二分图的联通块,用链表维护即可。
效率: O ( k ) O(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;
}