Luogu P10741 SEERC2020 Fence Job 题解 [ 蓝 ] [ 线性 dp ] [ 观察 ] [ 状态设计优化 ]
Fence Job:不错的观察类 dp 题!
观察
首先不难发现,假设某一段数字 \(a_l\sim a_r\) 最后全变成了 \(x\),那么 \(x\) 的位置 \(pos_x\) 一定满足:
- \(l \le pos_x \le r\)。
- \(\forall l \le i \le r,a_i \ge x\)。
那么,我们就可以 \(O(n^2)\) 求出每个数最大能覆盖的区间 \(a_l\sim a_r\)。
并且不难发现,我们最后的答案一定是由这些连续段拼接起来的。
dp 设计
因为有了这个连续段性质,所以我们就比较容易 dp 了。
本人一开始用的是 \(dp_{i,j}\) 表示考虑到第 \(i\) 位,第 \(i\) 位是原数列里第 \(j\) 个数的方案数。这种状态定义下利用前缀和优化 dp 其实是可以 \(O(n^2)\) 得到答案的,但是转移方程细节有点多,这里不再赘述。
但是实际上这题有更简单的一个状态定义,同样是 \(dp_{i,j}\),但是表示的却是考虑到第 \(j\) 位,这 \(j\) 个位的数字所处的下标全都在 \(1 \sim i\) 之间的方案数是多少(这里的 \(i,j\) 和前一个状态定义颠倒了一下,但是无伤大雅)。
这两种 dp 的状态定义的唯一区别就在于一个是限制了这一位必须是 \(j\),一个限制了这一位只要小于等于 \(j\) 即可。这种通过把固定某个元素转化为固定某个前缀的状态优化是一种常见的优化转移方式,无论是计数 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}\),即对应着一个元素单独成一个连续段的情况。
最后输出 \(dp_{n,n}\) 即可。
同时这个显然是可以用类似背包优化一下空间的,时间复杂度是 \(O(n^2)\)。不优化空间其实也能过。
代码
不优化版本
#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;
}

浙公网安备 33010602011771号