【BZOJ5133】[CodePlus2017年12月]白金元首与独舞 矩阵树定理

【BZOJ5133】[CodePlus2017年12月]白金元首与独舞

题面www.lydsy.com/JudgeOnline/upload/201712/div1.pdf

题解:由于k很小,考虑用矩阵树定理。

我们先预处理出:从每个已决策点,一直走下去会走到哪个未决策点(我们将最外面看作一个大的未决策点)。可以用拓扑排序搞定,若有环则无解。

然后我们枚举每个未决策点的四个方向,看一下一直走下去会走到哪个点,在新图中从这个点到终点连一条边。得到新图的出度矩阵和邻接矩阵,求出|出度矩阵-邻接矩阵|即可。

注:内向树:出度矩阵,外向树:入度矩阵。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#define p(A,B) (((A)-1)*m+(B))
using namespace std;
typedef long long ll;
const ll P=1000000007;

int T,n,m,tot,cnt;
ll ans;
int x[310],y[310],from[40010],to[40010],next[40010],head[40010];
ll v[310][310];
char str[210][210];
queue<int> q;
inline void add(int a,int b)
{
	to[cnt]=b,next[cnt]=head[a],head[a]=cnt++;
}
void work()
{
	scanf("%d%d",&n,&m);
	int i,j,k,a,b,u;
	memset(from,-1,sizeof(from)),memset(head,-1,sizeof(head)),cnt=tot=0;
	q.push(0),from[0]=0;
	for(i=1;i<=n;i++)
	{
		scanf("%s",str[i]+1);
		for(j=1;j<=m;j++)
		{
			if(str[i][j]=='.')	x[++tot]=i,y[tot]=j,q.push(p(i,j)),from[p(i,j)]=tot;
			else
			{
				a=i,b=j;
				if(str[i][j]=='L')	b--;
				if(str[i][j]=='R')	b++;
				if(str[i][j]=='U')	a--;
				if(str[i][j]=='D')	a++;
				if(!a||!b||a>n||b>m)	add(0,p(i,j));
				else	add(p(a,b),p(i,j));
			}
		}
	}
	while(!q.empty())
	{
		u=q.front(),q.pop();
		for(i=head[u];i!=-1;i=next[i])	from[to[i]]=from[u],q.push(to[i]);
	}
	for(i=1;i<=n*m;i++)	if(from[i]==-1)
	{
		puts("0");
		return ;
	}
	memset(v,0,sizeof(v));
	for(i=1;i<=tot;i++)
	{
		a=x[i],b=y[i];
		if(a<n)	v[i][from[p(a+1,b)]]--;
		if(a>1)	v[i][from[p(a-1,b)]]--;
		if(b<m)	v[i][from[p(a,b+1)]]--;
		if(b>1)	v[i][from[p(a,b-1)]]--;
		v[i][i]+=4;
	}
	for(i=1;i<=tot;i++)	for(j=1;j<=tot;j++)	if(v[i][j]<0)	v[i][j]+=P;
	for(ans=1,i=1;i<=tot;i++)
	{
		for(j=i;j<=tot;j++)	if(v[j][i])	break;
		if(j!=i)	for(ans=P-ans,k=i;k<=tot;k++)	swap(v[i][k],v[j][k]);
		for(j=i+1;j<=tot;j++)
		{
			ll A=v[i][i],B=v[j][i],tmp,temp;
			while(B)
			{
				tmp=A/B,temp=A,A=B,B=temp%B;
				for(ans=P-ans,k=i;k<=tot;k++)	v[i][k]=(v[i][k]-tmp*v[j][k]%P+P)%P,swap(v[i][k],v[j][k]);
			}
		}
		ans=ans*v[i][i]%P;
	}
	printf("%lld\n",ans);
}
int main()
{
	//freopen("C.in","r",stdin);
	scanf("%d",&T);
	while(T--)	work();
	return 0;
}

转载于:https://www.cnblogs.com/CQzhangyu/p/8125067.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值