题目

n*m(1<=n,m<=2e5,n*m<=2e5)的网格图,

有一些格子内已经放入了闹钟,当前时刻在[0,h)(1<=h<=1e9)之间

还有一些位置没有放闹钟,输入时用-1表示,

在放入这些闹钟的时候,你可以指定它们的初始时刻,只要在[0,h)即可

不同的闹钟,被指定的初始时刻可以不同

放完这些闹钟之后,你可以执行以下操作若干次,

每次选择一行或者一列,并将这些闹钟都往后调一个小时,

即,当前闹钟时刻x,x<h-1时,会被调到x+1;x=h-1时,会被调到0

求指定初始时刻的方案数,使得所有的闹钟可以被调到同一时刻,答案对1e9+7取模

实际用例总数t不超过5e4,但保证sum n*m<2e5

思路来源

乱搞ac

题解

其实和刚刚的abc280f很像,只是这个题更直接一些,

都有在线和离线两种做法,感觉很典中典

在线:带权并查集(hdu3047);离线:建树然后dfs判环(abc280f)

而abc280f这个题,主要是要找出非零环,

于是对图dfs得到一棵树后,检查非树边是否可以被树边线性表示

这个题也可以采用一样的做法,将同一行内的,值不为-1的两列,x列和y列,

连边y->x,权值(val[y]-val[x])%h;连边x->y,权值(val[x]-val[y])%h,就化成了abc280f这个题

于是只需检查,不同行对于列的限制,是否存在矛盾即可

对每一行做完之后,每一列就无需再做一遍了,

因为如果行的限制满足,势必存在一个delta,

使得第一行的x列的数,加delta后,等于第二行的x列的数;

第一行的y列的数,加delta后,等于第二行的y列的数;

检查完没有矛盾之后,

对于确定的数(不是-1的数)所在的位置(x,y),就直接将x行和y列这两个点直接连到一起,

连到一起后,当前图相当于被分成了若干个连通块,

而每将-1填成一个具体的数,都有h种选择,

并且,将(x,y)处的-1填成具体数后,

相当于将x行所在的联通和y列所在的连通块合并到一起

最后,需要将n行m列这n+m个点合并成一个连通块

设通过-1合并了x次,则答案为h的x次方,

感觉有点类似矩阵的秩

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=4e5+10,mod=1e9+7;
int t,n,m,h,par[maxn];
ll pos[maxn];
void init(int x){
	for(int i=0;i<x;++i){
		par[i]=i;
		pos[i]=0;
	}
}
int find(int x){
	if(par[x]==x)return x;
	int fa=par[x];//直接基类 
	par[x]=find(par[x]);//路径压缩 
	(pos[x]+=pos[fa])%=h;//树上前缀和 两链变一链 直接连树根 
	return par[x];//返回树根 
}
//y比x大v 
bool unite(int x,int y,ll v){
	int px=find(x),py=find(y);
	if(px==py){
		if((pos[x]+v-pos[y])%h)return 0;
		return 1;
	}
	//把y所在树的树根挂在x所在树的树根上 只改树根py 
	par[py]=px;//rank[py]=rank[y现在]-rank[y之前]==(rank[x]+v)-rank[y] 
	pos[py]=((pos[x]+v-pos[y])%h+h)%h;//注意rank可能有负 与将负旋为0的根是等价的
	return 1; 
} 
int solve(){
    vector<vector<ll> >a(n+1,vector<ll>(m+1,0));
    int all=0,cnt=0;
    init(m);
    for(int i=0;i<n;++i){
        for(int j=0;j<m;++j){
            scanf("%lld",&a[i][j]);
        }
    }
    for(int i=0;i<n;++i){
        int las=-1;
        for(int j=0;j<m;++j){
            if(a[i][j]==-1)continue;
            if(~las && !unite(las,j,(a[i][j]-a[i][las]+h)%h)){
                return 0;
            }
            las=j;
        }
    }
    /*
    init(n);
    for(int j=0;j<m;++j){
        int las=-1;
        for(int i=0;i<n;++i){
            if(a[i][j]==-1)continue;
            if(~las && !unite(las,i,a[i][j]-a[las][j])){
                return 0;
            }
            las=i;
        }
    }*/
    init(n+m);
    for(int i=0;i<n;++i){
        for(int j=0;j<m;++j){
            if(~a[i][j])par[find(n+j)]=find(i);
        }
    }
    for(int i=0;i<n+m;++i){
        if(find(i)==i)cnt++;
    }
    int res=1;
    for(int i=1;i<cnt;++i){
        res=1ll*h*res%mod;
    }
    //printf("res:%d h:%d\n",res,h);
    //printf("res:%d\n",res);
    return res;
}
int main(){
    scanf("%d",&t);
    for(int i=1;i<=t;++i){
        scanf("%d%d%d",&n,&m,&h);
        printf("%d\n",solve());
    }
	return 0;
} 
/*
hdu3047 带权并查集
1 2
-1 -1

1 -1
-1 2

1 0 -1
-1 -1 2

n+m-1个点 现在有3个点

-1 -1 -1
-1 -1 -1
n+m-1个点 现在有0个点
*/
12-08 08:06