[USACO 2020.1 Platinum][LOJ3246]Cave Paintings(bfs+树形dp)

题面

https://loj.ac/problem/3246

题解

注意到如果两个同在第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;
}
posted @ 2020-02-07 17:44  coder66  阅读(560)  评论(0编辑  收藏  举报