题解:P10741 [SEERC 2020] Fence Job
不错的观察类 dp 题!
观察
首先不难发现,假设某一段数字
那么,我们就可以
并且不难发现,我们最后的答案一定是由这些连续段拼接起来的。
dp 设计
因为有了这个连续段性质,所以我们就比较容易 dp 了。
本人一开始用的是
但是实际上这题有更简单的一个状态定义,同样是
这两种 dp 的状态定义的唯一区别就在于一个是限制了这一位必须是
那么预处理每个数能够拓展到的左右端点后直接转移即可:
- 当
l \le j \le r,dp_{i,j}\gets dp_{i-1,j}+dp_{i,j-1} ,即对应着接着上一个连续段的方案。 - 否则
dp_{i,j}\gets dp_{i-1,j} ,即对应着一个元素单独成一个连续段的情况。
最后输出
同时这个显然是可以用类似背包优化一下空间的,时间复杂度是
代码
不优化版本
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const ll mod=1e9+7;
const int N=5005;
int n,h[N];
ll dp[N][N];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>h[i];
for(int i=1;i<=n;i++)dp[i][0]=1;
for(int i=1;i<=n;i++)
{
int l=i,r=i;
while(l-1>=1&&h[l-1]>=h[i])l--;
while(r+1<=n&&h[r+1]>=h[i])r++;
for(int j=1;j<=n;j++)
{
dp[i][j]=dp[i-1][j];
if(l<=j&&j<=r)dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
}
}
cout<<dp[n][n];
return 0;
}
优化版本
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const ll mod=1e9+7;
const int N=5005;
int n,h[N];
ll dp[N];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>h[i];
dp[0]=1;
for(int i=1;i<=n;i++)
{
int l=i,r=i;
while(l-1>=1&&h[l-1]>=h[i])l--;
while(r+1<=n&&h[r+1]>=h[i])r++;
for(int j=l;j<=r;j++)dp[j]=(dp[j]+dp[j-1])%mod;
}
cout<<dp[n];
return 0;
}