[LOJ6259][CodePlus 2017 12 月赛]白金元首与独舞-矩阵树定理

白金元首与独舞

题目描述

到河北省 见斯大林 / 在月光下 你的背影 / 让我们一起跳舞吧
うそだよ~ 河北省怎么可能有 Stalin。

可是…… 可是如果 Stalin 把自己当作炸弹扔到地堡花园里来了呢?

怀揣着这份小小的希望,元首 Adolf 独自走进了花园。终有一天会重逢的吧,Stalin。或许是在此处,或许是在遥远的彼方。

无论如何,在此之前,好好装点一番花园,编排一段优美的舞步吧!

元首把花园分为 n n n m m m 列的网格。每个格子中都可以放置一个标识,指向上、下、左、右四个方向中的任意一个。元首位于一个格子时,会按照其中标识所指的方向进入周围的格子,或者走出花园(即目的格子不在网格之内)。举个例子 —— 对于下面的放置方式,元首从第 3 3 3 行第 2 2 2 列的格子开始,会沿着以红色标出的路径走出花园;从第 2 2 2 行第 2 2 2 列的格子开始,则会在以蓝色标出的环路内不断地行走。

← → ↓

← ↑ ←

↓ ← ↑

→ ↓ ←

元首已经设计好了大部分格子的标识。元首用字符 L、R、U、D 分别表示指向左、右、上、下四个方向的标识,用字符 . 表示未决定的格子。现在,元首希望将每个 . 替换为 L、R、U、D 中任意一种,使得从花园中的任意一个格子出发,按照上述规则行走,都可以最终走出花园。

你需要编写程序帮助元首计算替换的不同方案数。两个方案不同当且仅当存在一个格子,使得两个方案中该格子内的标识不同。当然,由于答案可能很大,只需给出方案数除以 1 0 9 + 7 10^9 + 7 109+7所得的余数即可。

输入格式

从标准输入读入数据。

输入的第一行包含一个正整数 T T T —— 测试数据的组数。接下来包含 T T T 组测试数据,格式如下,测试数据间没有空行。

1 1 1 行:两个空格分隔的正整数 n n n m m m —— 依次表示花园被分成的行数和列数。
接下来 n n n 行:每行一个长度为 m m m 的由字符 L、R、U、D 和 . 组成的字符串 —— 表示花园内已经确定的格子状态。

输出格式

输出到标准输出。

对于每组测试数据输出一行 —— 满足条件的方案数除以 1 0 9 + 7 10^9 + 7 109+7 所得的余数。

样例输入

5
3 9
LLRRUDUUU
LLR.UDUUU
LLRRUDUUU
4 4
LLRR
L.LL
RR.R
LLRR
4 3
LRD
LUL
DLU
RDL
1 2
LR
2 2

样例输出

3
8
0
1
192

样例解释

1 1 1 组数据中,将惟一的 . 替换成 R、U 或 D 均满足要求。

2 2 2 组数据中,将左上方和右下方的两个 . 分别替换成 LR、LU、LD、UR、UU、UD、DR 或 DD 均满足要求。

3 3 3 组数据中,没有待决定的格子,原本的安排会使得元首陷入无尽的环路,故答案为 0 0 0。该组数据与【题目描述】中的例子相同。

4 4 4 组数据中,也没有待决定的格子,但原本的安排已经满足要求,故答案为 111。

数据范围与提示

k k k 表示标记未确定(即包含 “.”)的格子总数。

对于所有数据,有 1 ≤ T ≤ 10 1 \leq T \leq 10 1T10 1 ≤ n , m ≤ 200 1 \leq n, m \leq 200 1n,m200 0 ≤ k ≤ min ⁡ ( n m , 300 ) 0 \leq k \leq \min(nm, 300) 0kmin(nm,300)

“… wie Stalin!”

题面与史实无关。


好题一道。
然而有向图的生成树计数什么的完全不会…


思路:

考虑把外界视为一个节点,所有的’.'视为一个节点,对于每个节点,向该节点的四个方向中每个方向最近能走到的节点连一条边,走出边界则连向外界点。
这样我们得到了一张有向图。这张图每个节点到外界节点的一条路径便是该节点可以走到边界的合法的一种方案。

那么原问题相当于询问这张有向图有多少种方案,使得在每个节点只保留一条出边的情况下,所有点与根节点联通——换句话说就是生成内向树的个数。

然而已知的生成树求法仅适用于无向图,怎么办呢?
其实,只需要把基尔霍夫矩阵中的度数矩阵改为出度矩阵,同时余子式选择消去根节点所在的行和列,然后直接矩阵树定理求行列式即可~

于是剩下的,就是并查集判无解、dfs求每个方向的临近点、高斯消元之类的了~

以下这份代码可能比较混乱,请见谅…

#include<iostream>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

typedef long long ll;
typedef pair<int,int> pr;
const int N=309;
const ll md=1e9+7;

ll n,m,a[N][N],id,t;
int mp[N][N],fa[N*N];
char g[N][N];
int vis[N][N];
vector<pr> v;

inline int find(int x){return fa[x]=(fa[x]==x?x:find(fa[x]));}

inline ll det(ll a[][N])
{
	ll ret=1;
	for(int i=1;i<n;i++)
		for(int j=1;j<n;j++)
			a[i][j]=(a[i][j]%md+md)%md;
	for(int i=1;i<n;i++)
		for(int j=i+1;j<n;j++)
		{
			ll x=a[i][i],y=a[j][i],z,l;
			while(y)
			{
				z=x/y,l=x,x=y,y=l%y;
				for(int k=i;k<n;k++)
				{
					a[i][k]=(a[i][k]-z*a[j][k]%md+md)%md;
					swap(a[i][k],a[j][k]);
				}
				ret=-ret;
			}
		}
	for(int i=1;i<n;i++)
		ret=ret*a[i][i]%md;
	return (ret%md+md)%md;
}

int dx[]={-1,1,0,0};
int dy[]={0,0,-1,1};

inline int trans(char c)
{
	switch(c)
	{
		case 'U':return 0;
		case 'D':return 1;
		case 'L':return 2;
		case 'R':return 3;
	}
}

inline bool in(int x,int y){return 1<=x && x<=n && 1<=y && y<=m;}

inline int pt(int x,int y){if(!in(x,y))return 0;return (x-1)*m+y;}

inline int dfs(int x,int y)
{
	if(vis[x][y]==t)return -1;
	vis[x][y]=t;
	if(~mp[x][y])return mp[x][y];
	else if(!in(x,y))return 0;
	else return dfs(x+dx[g[x][y]],y+dy[g[x][y]]);
}

int mian()
{
	scanf("%d%d",&n,&m);
	memset(a,0,sizeof(a));
	memset(mp,0,sizeof(mp));
	memset(vis,0,sizeof(vis));
	id=0;t=1;v.clear();

	for(int i=0;i<=n*m;i++)
		fa[i]=i;

	for(int i=1;i<=n;i++)
	{
		scanf("%s",g[i]+1);
		for(int j=1;j<=m;j++)
			if(g[i][j]=='.')
			{
				mp[i][j]=++id;
				v.push_back(pr(i,j));
			}
			else
			{
				mp[i][j]=-1;
				g[i][j]=trans(g[i][j]);
				fa[find(pt(i,j))]=find(pt(i+dx[g[i][j]],j+dy[g[i][j]]));
			}
	}

	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(mp[i][j]==-1)
			{
				int fat=find(pt(i,j));
				if(fat==0)continue;
				int y=fat%m,x=fat/m+1;
				if(y==0)y=m,x--;
				if(!in(x,y) || mp[x][y]==-1)
					return puts("0"),0;
			}

	for(int i=0;i<v.size();i++)
	{
		int x=v[i].first,y=v[i].second,id=mp[x][y];
		for(int j=0;j<=3;j++,t++)
			if(in(x+dx[j],y+dy[j]))
			{
				int pt=dfs(x+dx[j],y+dy[j]);
				if(pt!=-1 && pt!=id)a[id][id]++,a[id][pt]--;
			}
			else
				a[id][id]++,a[id][0]--;
	}

	n=id+1;
	printf("%lld\n",det(a));	
}

int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
		mian();

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值