走进矩阵树定理--「CodePlus 2017 12 月赛」白金元首与独舞

n,m<=200,n*m的方阵,有ULRD表示在这个格子时下一步要走到哪里,有一些待决策的格子用.表示,可以填ULRD任意一个,问有多少种填法使得从每个格子出发都能走出这个方阵,答案取模。保证未确定的格子<=300。

。。。一脸懵逼地写了原本30实际20的暴力然后跪着啃了下论文

然而什么都没啃懂不如结论记下来:

首先矩阵行列式的定义:一个n*n的矩阵,行列式值为$\sum_{b是n的一个排列} \ \ \ \ \ ( (-1)^{b的逆序对数} \ \ \ \ \ * \prod_{i=1}^{n} A_{i,b_i})$

矩阵A行列式的性质:

|A|=|A的转置|

|AB|=|A||B|

|A|+|B|=|A和B的某一行加起来其他不变的矩阵|

交换两行得B,|B|=-|A|,因为每次计算一个排列时逆序对数都相差1。

A的某行乘个x得B,|B|=x|A|。

A的某行乘某个数加到另一行上得B,|B|=|A|。由第三条可得。

每行每列和为0的矩阵行列式为0。

由四五六条可用高斯消元计算行列式。

基尔霍夫矩阵:

无向图:矩阵对角线上是度数,其他如果对应边存在就是-1,不然就是0。

有向图:矩阵对角线上是入度,其他如果对应边存在就是-1,不然就是0。

矩阵树定理:一个无向图的基尔霍夫矩阵的任意一个n-1阶的子矩阵,即删掉了第i行和第i列,的行列式是这个图的生成树个数。

一个有向图以点i为根的生成树个数是基尔霍夫矩阵去掉第i行和第i列剩下的矩阵的行列式。

嗯然后就是这道题。

首先,如果在外界虚拟一个节点,把所有指出去的格子都指向它,把所有确定格子向指向的点连边,可得一个有向图。

然后,待确定的点向四个方向都连边,求这个图以外界点为根的反向的树形图即可。为什么呢,首先确定的格子的边一定会选到,因为每个格子只有一条出边,不然他就和其他点断掉了;其次那些待确定的格子为了形成树,只会在四个方向里选一个。

嗯这样只能拿50分。

可以发现那些已经确定的点是多余的,可以缩掉。也就是给所有待确定格子编号,外界点编号0,然后预处理他往上下左右走能遇到的第一个有编号的格子,朝他们连边即可。

然后就大功告成了。

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<algorithm>
  4 #include<stdlib.h>
  5 //#include<queue>
  6 #include<math.h>
  7 //#include<time.h>
  8 //#include<iostream>
  9 using namespace std;
 10
 11 int n,m,K,T;
 12 const int mod=1e9+7;
 13 #define maxn 311
 14 int pos[maxn][maxn],who[maxn][maxn],place[maxn][4]; char mp[maxn][maxn];
 15
 16 int powmod(int a,int b)
 17 {
 18     int ans=1;
 19     while (b)
 20     {
 21         if (b&1) ans=1ll*ans*a%mod;
 22         a=1ll*a*a%mod;
 23         b>>=1;
 24     }
 25     return ans;
 26 }
 27
 28 struct Mat
 29 {
 30     int num[maxn][maxn];
 31     void clear() {memset(num,0,sizeof(num));}
 32 }mat;
 33 int gauss()
 34 {
 35     int ans=1;
 36     for (int i=1;i<=K;i++)
 37     {
 38         int id=i;
 39         for (int j=i+1;j<=K;j++) if (fabs(mat.num[j][i])>fabs(mat.num[id][i])) id=j;
 40         if (id!=i)
 41         {
 42             ans=1ll*ans*(mod-1)%mod;
 43             for (int j=i;j<=K;j++) {int t=mat.num[i][j]; mat.num[i][j]=mat.num[id][j]; mat.num[id][j]=t;}
 44         }
 45         int tmp=powmod(mat.num[i][i],mod-2);
 46         for (int j=i+1;j<=K;j++)
 47             for (int k=K;k>=i;k--)
 48                 mat.num[j][k]-=mat.num[i][k]*1ll*mat.num[j][i]%mod*tmp%mod,
 49                 mat.num[j][k]+=mat.num[j][k]<0?mod:0;
 50     }
 51     for (int i=1;i<=K;i++) ans=1ll*ans*mat.num[i][i]%mod;
 52     return ans;
 53 }
 54
 55 int vis[maxn][maxn],Time; bool die;
 56 void dfs(int x,int y)
 57 {
 58     if (mp[x][y]==‘.‘) return;
 59     if (vis[x][y])
 60     {
 61         if (pos[x][y]==-1) die=1;
 62         return;
 63     }
 64     vis[x][y]=1;
 65     int tx=x,ty=y;
 66     if (mp[x][y]==‘U‘) x--;
 67     else if (mp[x][y]==‘D‘) x++;
 68     else if (mp[x][y]==‘L‘) y--;
 69     else if (mp[x][y]==‘R‘) y++;
 70     if (x<1 || x>n || y<1 || y>m) pos[tx][ty]=0;
 71     else pos[tx][ty]=-1,dfs(x,y),pos[tx][ty]=pos[x][y];
 72 }
 73 int check(int x,int y)
 74 {
 75     if (x<1 || x>n || y<1 || y>m) return 0;
 76     return pos[x][y];
 77 }
 78
 79 int main()
 80 {
 81     scanf("%d",&T);
 82 while (T--)
 83 {
 84     scanf("%d%d",&n,&m); K=0; Time=0; die=0;
 85     memset(vis,0,sizeof(vis));
 86     memset(pos,0,sizeof(pos));
 87     for (int i=1;i<=n;i++) scanf("%s",mp[i]+1);
 88     for (int i=1;i<=n;i++)
 89         for (int j=1;j<=m;j++)
 90             if (mp[i][j]==‘.‘) pos[i][j]=++K;
 91     for (int i=1;i<=n;i++)
 92         for (int j=1;j<=m;j++)
 93             if (mp[i][j]!=‘.‘) dfs(i,j);
 94     if (die) {puts("0"); continue;}
 95
 96     for (int i=1;i<=n;i++)
 97         for (int j=1;j<=m;j++)
 98             if (mp[i][j]==‘.‘)
 99             {
100                 int nx=i,ny=j,now=pos[i][j];
101                 nx++; place[now][0]=check(nx,ny); nx--;
102                 nx--; place[now][1]=check(nx,ny); nx++;
103                 ny++; place[now][2]=check(nx,ny); ny--;
104                 ny--; place[now][3]=check(nx,ny); ny++;
105             }
106     mat.clear();
107     for (int i=1;i<=K;i++)
108     {
109         for (int j=0;j<4;j++) mat.num[place[i][j]][i]--;
110         mat.num[i][i]+=4;
111     }
112     for (int i=1;i<=K;i++)
113         for (int j=1;j<=K;j++)
114             if (mat.num[i][j]<0) mat.num[i][j]+=mod;
115     printf("%d\n",gauss());
116 }
117     return 0;
118 }

原文地址:https://www.cnblogs.com/Blue233333/p/8126582.html

时间: 2024-10-11 00:49:34

走进矩阵树定理--「CodePlus 2017 12 月赛」白金元首与独舞的相关文章

「CodePlus 2017 12 月赛」白金元首与独舞

description 题面 data range \[ 1 \leq T \leq 10, 1 \leq n, m \leq 200 , 0 \leq k \leq \min(nm, 300)\] solution 矩阵树定理 求无向图的生成树个数 度数矩阵-邻接矩阵 去掉一行一列求行列式 为了保证精度可以辗转相除 这里是模意义下的 const int mod=998244353; int a[305][305]; il int gauss(int n){ RG int ans=1; for(

「CodePlus 2017 12 月赛」火锅盛宴(模拟+树状数组)

1A,拿来练手的好题 用一个优先队列按煮熟时间从小到大排序,被煮熟了就弹出来. 用n个vector维护每种食物的煮熟时间,显然是有序的. 用树状数组维护每种煮熟食物的数量. 每次操作前把优先队列里煮熟时间<=当前时间的弹出,BIT上+1. 每次0操作把食物塞进优先队列和vector 每次1操作先看看树状数组里有没有数,没有输出angry,有的话在树状数组上二分找到最小的数. 每次2操作先看看树状数组里有没有这一种数,有的话输出并-1,没有的话看看vector有没有,有的话输出时间差,没有的话输出

「CodePlus 2017 12 月赛」火锅盛宴

n<=100000种食物,给每个食物煮熟时间,有q<=500000个操作:在某时刻插入某个食物:查询熟食中编号最小的并删除之:查询是否有编号为id的食物,如果有查询是否有编号为id的熟食,如果有熟食删除之,否则输出其离煮熟的最小时间:查询编号在[L,R]的熟食有多少.保证操作时间递增. 可以发现:针对某种食物的信息维护只需要知道其未煮熟的食物中煮熟时间的最小值,而所有的熟食都可以一起维护.为此可以:对每个食物开个队列存未熟食物,对所有食物开个优先队列以便及时把熟食从队列中提出来,并开个以编号为

【LIbreOJ】#6256. 「CodePlus 2017 12 月赛」可做题1

[题意]定义一个n阶正方形矩阵为"巧妙的"当且仅当:任意选择其中n个不同行列的数字之和相同. 给定n*m的矩阵,T次询问以(x,y)为左上角的k阶矩阵是否巧妙.n,m<=500,T<=10^5. [算法]数学 [题解] 可以证明每个矩阵是巧妙的当且仅当其每个2阶子矩阵均是巧妙的: 必要性:若该矩阵有一个不巧妙的2阶子矩阵,则其他部分选择相同的情况下(不涉及此两行列),这两行列的和不同,所以该矩阵不是巧妙的. 观察一个巧妙的2阶子矩阵 a1 a2 b1 b2 由a1+b2=a

[LOJ 6249]「CodePlus 2017 11 月赛」汀博尔

Description 有 n 棵树,初始时每棵树的高度为 H_i,第 i 棵树每月都会长高 A_i.现在有个木料长度总量为 S 的订单,客户要求每块木料的长度不能小于 L,而且木料必须是整棵树(即不能为树的一部分).现在问你最少需要等多少个月才能满足订单. Input 第一行 3 个用空格隔开的非负整数 n,S,L,表示树的数量.订单总量和单块木料长度限制.第二行 n 个用空格隔开的非负整数,依次为 H1,H2,…,Hn.第三行 n 个用空格隔开的非负整数,依次为 A1,A2,…,An. Ou

「CodePlus 2017 11 月赛」Yazid 的新生舞会(树状数组/线段树)

学习了新姿势..(一直看不懂大爷的代码卡了好久T T 首先数字范围那么小可以考虑枚举众数来计算答案,设当前枚举到$x$,$s_i$为前$i$个数中$x$的出现次数,则满足$2*s_r-r > 2*s_l-l$的区间$[l+1,r]$其众数为$x$,这个显然可以用一个数据结构来维护. 直接扫一遍效率是$O($数字种类数$*nlogn)$的,无法承受,但是我们发现,对于每一段非$x$的数,$2*s_i-i$是公差为$-1$的等差数列,所以它们对答案的贡献实际上可以一次性计算.设$L$为一段非$x$数

「CodePlus 2017 11 月赛」汀博尔 (二分答案)

题目链接:https://loj.ac/problem/6249 题意:有 n 棵树,初始时每棵树的高度为 H?i?,第 i 棵树每月都会长高 A?i??.现在有个木料长度总量为 S 的订单,客户要求每块木料的长度不能小于 L,而且木料必须是整棵树(即不能为树的一部分). 现在问你最少需要等多少个月才能满足订单.(数据范围:1<=n<=200000,1<=S,L<=1e18,1<=Hi,Ai<=1e9) 题解:很显然二分答案.上界不能直接选择1e18,会爆long lo

「CodePlus 2017 11 月赛」可做题

这种题显然先二进制拆位,显然改的位置显然只有每一段确定的数的开头和结尾,只需要对于每一个可决策位置都尝试一下填1和0,然后取min即可. #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<algorithm> #define ll long long using namespace std; const int maxn=500010;

[spoj104][Highways] (生成树计数+矩阵树定理+高斯消元)

In some countries building highways takes a lot of time... Maybe that's because there are many possiblities to construct a network of highways and engineers can't make up their minds which one to choose. Suppose we have a list of cities that can be c