[loj3246]Cave Paintings

题中所给的判定条件似乎比较神奇,那么用严谨的话来说就是对于两个格子(x,y)和(x',y'),如果满足:
1.$xle x'$;
2.从(x,y)通过x,x+1,……,n行,允许向四个方向走,不允许经过石头的位置/画外,可以到达(x',y')
若(x,y)有水,那么(x',y')也有水
这个性质似乎并没有什么用,考虑对于$x=x'$的情况,相当于这些y必须都是同一个状态,容易想到一个思路:从下往上进行枚举,用一个并查集维护这一行的所有点,然后将这一行与下一行相邻的点连起来,形成这一行的并查集
通过并查集,可以将同一行内在同一个并查集内的点缩起来,并向下面的点连边,容易发现一定是森林
(证明:如果存在上面两个点同时指向其中一个点,那么这两个点肯定会缩起来)
那么题中的条件就有意义了:相当于如果一个点的根选择了1,那么整个子树都必须选1,求方案数,树形dp即可

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define N 1005
 4 #define mod 1000000007
 5 int V,n,m,ans,f[N*N],dp[N*N],bl[N][N];
 6 char s[N][N];
 7 int find(int k){
 8     if (k==f[k])return k;
 9     return f[k]=find(f[k]);
10 }
11 void merge(int x,int y){
12     if (find(x)==find(y))return;
13     dp[find(y)]=1LL*dp[find(x)]*dp[find(y)]%mod; 
14     f[find(x)]=find(y);
15 }
16 int main(){
17     scanf("%d%d",&n,&m);
18     for(int i=1;i<=n;i++)scanf("%s",s[i]);
19     for(int i=n;i;i--){
20         int las=V;
21         for(int j=1;j<m-1;j++)
22             if (s[i][j]=='.'){
23                 if (s[i][j-1]=='#'){
24                     dp[++V]=1;
25                     f[V]=V;
26                 }
27                 bl[i][j]=V;
28                 if (s[i+1][j]=='.')merge(bl[i+1][j],V);
29             }
30         for(int j=las+1;j<=V;j++)
31             if (find(j)==j)dp[j]=(dp[j]+1)%mod;
32     }
33     ans=1;
34     for(int i=1;i<=V;i++)
35         if (find(i)==i)ans=1LL*ans*dp[i]%mod;
36     printf("%d",ans);
37 }
View Code
原文地址:https://www.cnblogs.com/PYWBKTDA/p/12244092.html