[USACO 2020 Open Platinum]Sprinklers 2: Return of the Alfalfa
Description
Farmer John 有一块小的田地,形状为一个 \(N\) 行 \(N\) 列的一个方阵,对于所有的 \(1 \le i,j \le N\),从上往下的第 \(i\) 行的从左往右第 \(j\) 个方格记为 \((i,j)\)。他有兴趣在他的田地里种植甜玉米和苜蓿。为此,他需要安装一些特殊的洒水器。
在方格 \((I,J)\) 中的甜玉米洒水器可以喷洒到所有左下方的方格:即满足 \(I \le i\) 以及 \(j \le J\) 的 \((i,j)\)。
在方格 \((I,J)\) 中的苜蓿洒水器可以喷洒到所有右上方的方格:即满足 \(i \le I\) 以及 \(J \le j\) 的 \((i,j)\)。
被一个或多个甜玉米洒水器喷洒到的方格可以长出甜玉米;被一个或多个苜蓿洒水器喷洒到的方格可以长出苜蓿。但是被两种洒水器均喷洒到(或均喷洒不到)的方格什么也长不出来。
帮助 Farmer John 求出在他的田地里安装洒水器的方案数(\(\bmod \ 10^9 + 7\)),每个方格至多安装一个洒水器,使得每个方格均能生长作物(即被恰好一种洒水器喷洒到)。
某些方格正被长毛奶牛占据;这不会阻止这些方格生长作物,但是这些方格里不能安装洒水器。
\(N \leq 2000\)
Solution
显然最后的状态会是中间有一条折线,左边是一种喷水器,右边是另一种,每个拐角处都必须安装,而其他地方随便安装(且种类确定)
设状态\(dp[i][j]\)代表喷左下的喷水器的最右下的一个在\((i,j)\)的方案数目(这个方案数目不包含右下角所填方案数,因为一会儿还要再往下转移),而\(s[i][j]\)代表\((i,j)\)右下方(包括i行和j列)一共有多少个可以填的地方
那么答案显然就是\(\sum dp[i][j] \times (s[i][j+1] - [j!=n]) \times [mapn[n][j+1]==1]\)
那考虑怎么转移,显然每个\(dp[i][j]\)都可以从\(dp[k][l](k<i,l<j)\)转移来
设上一个喷水器在\((k,l)\)(即深红色块,而浅红色块是\((i,j)\)),那么对于图示这一块灰色地区,是可以任意填的,
(图片来自洛谷题解,用户为水印所示)
所以得到一个很棒的转移:
\(dp[i][j]=\sum dp[k][l] \times 2^{s[k][l+1]-s[i][j+1]-1-[i>1]} \times [mapn[i-1][l+1]==1]\)
其中的细节一是特判边界是否要填另一种洒水器,二是判断假如要填的话能不能填上
但是这个转移还是\(O(n^4)\)的呀 那不行
所以我们转化一下:
\(dp[i][j]=\sum (dp[k][l] \times {2^s[k][l+1]} \times [mapn[i-1][l+1]==1]) \times 2^{-s[i][j+1]-1-[i>1]}\)
那么显然前面那个东西可以二维前缀和一下:
\(pre[i-1][l]=\sum (dp[from 1 to i-1][l] \times 2^{s[from 1 to i-1][l+1]}) \times [mapn[i-1][l+1]==1]\)
\(pre2=pre[i-1][from 1 to j-1]\)
然后就可以快乐\(O(n^2)\)转移了,代码是很水的,就是边界特判有点烦
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int n;
int poww[4000010];
int ipow[4000010];
char ch[2010];
bool mapn[2010][2010];
int s[2010][2010];
int pre[2010];
int dp[2010][2010];
signed main(){
scanf("%lld",&n);
poww[0]=ipow[0]=1;
for(int i=1;i<=n*n;++i)poww[i]=2*poww[i-1]%mod,ipow[i]=(mod/2+1)*ipow[i-1]%mod;
for(int i=1;i<=n;++i){
scanf("%s",ch+1);
for(int j=1;j<=n;++j){
mapn[i][j]=(ch[j]=='.');
}
}
for(int i=1;i<=n;++i)mapn[0][i]=1;
for(int i=n;i>=0;--i){
for(int j=n;j>=0;--j){
s[i][j]=s[i][j+1]+s[i+1][j]-s[i+1][j+1]+mapn[i][j];
}
}
dp[0][0]=1;
pre[0]=poww[s[1][1]];
for(int i=1;i<=n;++i){
int pre2=0;
for(int j=1;j<=n;++j){
pre2=(pre2+(pre[j-1]*mapn[i-1][j])%mod)%mod;
if(!mapn[i][j])continue;
dp[i][j]=(pre2*ipow[s[i][j+1]+1+(i>1)])%mod;
}
for(int j=1;j<=n;++j)pre[j]=(pre[j]+(dp[i][j]*poww[s[i][j+1]])%mod);
}
int ans=0;
for(int i=0;i<n;++i){
ans=(ans+((pre[i]*mapn[n][i+1])%mod*ipow[1])%mod)%mod;
}
ans=(ans+pre[n])%mod;
printf("%lld\n",ans);
}