[USACO 2020.1 Platinum][LOJ3246]Cave Paintings(bfs+树形dp)
题面
题解
注意到如果两个同在第i行的空格子,如果它们能够只通过原图的i~n行这部分实现连通,那么它们最终的状态一定是相同的。
考虑把所有这样的格子合并。也就是,我们将每一行的空格子分别染色,某一行中两个格子的颜色相同当且仅当这两个格子符合上述条件。石头格子没有颜色。
最下面一行的染色方案显然。而只需要经过bfs,就可以用第i+1行的染色方案推出第i行的染色方案。
染色结束后,我们可以把每一个二元组\((i,j)\)(表示所有第i行的第j种颜色的格子)视为一个点,然后连边,\((i_1,j_1)\)到\((i_2,j_2)\)有一条单向边当且仅当\(i_2=i_1+1\),并且\({\exists}x{\in}(1,n)\),s.t.第\(i_1\)行第x个格子为空、颜色是\(j_1\),且第\(i_2\)行第x个格子为空、颜色是\(j_2\)。
容易发现这样的话每个点只会有一条入边。连完边后,就形成了一棵森林,就可以使用下面的递推式计算点u的答案:
\[dp(u)=1+{\prod\limits_{v{\in}son[u]}}dp(v)
\]
- 其中1是u点被灌水的情况,那么其子树必须被灌水。否则,u的各子节点独立,可以使用乘法原理合并。
最终答案即为森林中所有的根节点的dp值之积。
时间复杂度为O(n)。
代码
#include<bits/stdc++.h>
using namespace std;
#define N 1000
#define mod 1000000007
#define rg register
#define ll long long
inline int read(){
int s = 0,ww = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')ww = -1;ch = getchar();}
while('0' <= ch && ch <= '9'){s = 10 * s + ch - '0';ch = getchar();}
return s * ww;
}
int n,m;
inline int id(int i,int j){ //将“第i行的第j种颜色”这个二元组编号压入一维
return (i - 1) * m + j;
}
char s[N+5][N+5];
bool vis[N*N+5];
int clr[N+5][N+5],cn[N+5],head[N*N+5],fa[N*N+5];//clr表示颜色(连通性),cn表示每一行的颜色个数
int cnt;
struct edge{
int next,des;
}e[N*N+5];
inline void addedge(int a,int b){
cnt++;
e[cnt].des = b;
e[cnt].next = head[a];
head[a] = cnt;
}
inline ll dp(int u){ //每一个u代表一个(i,j)的二元组,表示第i行的颜色j
ll cur = 1;
vis[u] = 1;
for(rg int i = head[u];i;i = e[i].next){
int v = e[i].des;
cur = cur * dp(v) % mod;
}
return (cur + 1) % mod;
}
struct Graph{ //用来转移clr的辅助图
int cnt;
vector<int>link[3*N+5];
int f[3*N+5];
queue<int>q;
inline void reset(){
cnt = 0;
for(rg int i = 1;i <= 3 * m;i++)link[i].resize(0);
memset(f,0,sizeof(f));
}
inline void addedge(int a,int b){
link[a].push_back(b);
link[b].push_back(a);
}
inline bool bfs(int s){
bool flag = 0;
q.push(s);
while(!q.empty()){
int u = q.front();
q.pop();
for(rg int i = 0;i < link[u].size();i++){
int v = link[u][i];
if(!f[v]){
if(v <= m)flag = 1;
f[v] = f[u];
q.push(v);
}
}
}
return flag;
}
inline void calc(int i){
reset();
for(rg int j = 1;j < m;j++){
if(s[i][j] == '.' && s[i][j+1] == '.')addedge(id(1,j),id(1,j+1));
if(s[i+1][j] == '.' && s[i+1][j+1] == '.')addedge(id(2,j),id(2,j+1));
if(s[i][j] == '.' && s[i+1][j] == '.')addedge(id(1,j),id(2,j));
if(s[i+1][j] == '.')addedge(id(2,j),id(3,clr[i+1][j]));
} //id(1,…)和(2,…)代表原图的第i,i+1行;如果原图第i+1行第j格颜色为c,那么辅助图中id(2,j)就要向id(3,c)连边
for(rg int j = 1;j <= cn[i+1];j++){
if(f[id(3,j)])continue;
f[id(3,j)] = ++cn[i];
if(!bfs(id(3,j)))cn[i]--; //第i+1行颜色为j的这一块不与第i行的任何块连通
}
for(rg int j = 1;j < m;j++)if(f[id(1,j)])clr[i][j] = f[id(1,j)];
}
}G;
int main(){
freopen("cave.in","r",stdin);
freopen("cave.out","w",stdout);
n = read(),m = read();
bool flag = 1;
for(rg int i = 1;i <= n;i++)scanf("%s",s[i] + 1);
for(rg int i = n - 1;i >= 2;i--){
G.calc(i);
for(rg int j = 2;j < m;j++)if(s[i][j] == '.' && s[i+1][j] == '.')
fa[id(i+1,clr[i+1][j])] = id(i,clr[i][j]);
for(rg int j = 1;j <= cn[i+1];j++)
if(fa[id(i+1,j)])addedge(fa[id(i+1,j)],id(i+1,j));
for(rg int j = 2;j < m;j++)if(s[i][j] == '.' && !clr[i][j]){
if(s[i][j-1] == '.')clr[i][j] = clr[i][j-1];
else clr[i][j] = ++cn[i];
}
}
ll ans = 1;
for(rg int i = 2;i < n;i++)
for(rg int j = 2;j < m;j++){
if(s[i][j] == '.' && !vis[id(i,clr[i][j])])ans = ans * dp(id(i,clr[i][j])) % mod;
}
cout << ans << endl;
return 0;
}