E. Tick, Tock[带权并查集]

发布时间 2023-08-21 21:12:02作者: qbning

Problem - E - Codeforces

似乎带权并查集和dfs有难舍难分的关系......


题意是给一个n*m的矩阵,然后运算是模h的,-1的是不确定的,每次你可以选择一整行或者一整列+1,问有多少种填不确定的方式来使填后的矩阵经过若干次操作后能相等.

感觉有点像魔方.

行操作和列操作是可以交换顺序的,换言之,我们可以一直进行行操作,然后直到能明显看到列操作后相等为止.进一步说,如果可行那么对于任意两行元素的差值应该是相等的.此时对行规整之后对一整列进行操作即可使整个矩阵一致.

上图第一行先整体-1,然后剩下就可以逐列进行操作完成了.

VP时没想到带权并查集和图论DFS,看cup-pyy大佬的题解才明白过来.

下面的行和列调换也是等价的.

两行可以确定的话就用带权并查集拴起来,如果有矛盾则说明不存在.

无法联通的两行说明没有共同的非-1列,因此要再任意两个非联通行之间进行连接有h种可能,即连通块数目-1

查看代码
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll n,m,h;
ll f[200005],w[200005];
ll fin(ll x)
{
	if(f[x]!=x)
	{
		ll rt=fin(f[x]);
		w[x]=(w[x]+w[f[x]]+h)%h;
		f[x]=rt;
	}
	return f[x];
}
ll get(ll x,ll y)
{
	return (w[x]-w[y]+h)%h;
}
void merge(ll x,ll y,ll z)
{
	ll xx=fin(x),yy=fin(y);
	if(xx!=yy)
	{
		w[xx]=(z-w[x]+w[y]+h)%h;
		if(w[xx]<0)w[xx]+=h;
		f[xx]=f[yy];
	}
}
ll qow(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1)ans=(ans*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return ans;
}
void solve()
{
	cin>>n>>m>>h;
	for(int i=1;i<=n;i++)f[i]=i,w[i]=0;
	vector<vector<int> >a(n+1,vector<int>(m+1));
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		cin>>a[i][j];
	}
	for(int i=1;i<=m;i++)
	{
		ll last=-1;
		for(int j=1;j<=n;j++)
		{
			if(a[j][i]!=-1)
			{
				if(last!=-1)//记得last只能在a[j][i]!=-1的时候才能更新
				{
					if(fin(last)==fin(j))
					{
						if(get(last,j)!=(a[last][i]-a[j][i]+h)%h)//存在矛盾 
						{
							cout<<"0\n";
							return;
						}
					}else merge(last,j,(a[last][i]-a[j][i]+h)%h);//可以合并的时候进行合并 
				}last=j;
			}
		}
	}
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		if(fin(i)==i)ans++;
	} 
	ans--;//统计还需要多少条边
	for(int j=1;j<=m;j++)
	{
		bool c=1;
		for(int i=1;i<=n;i++)
		{
			if(a[i][j]!=-1)
			{
				c=0;
				break;
			}
		}
		ans+=c;//统计全-1列
	}
	cout<<qow(h,ans)<<'\n';
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	solve();
	return 0;
 }