F. Fence Job (SEERC 2020)

F. Fence Job

输入序列a[n]。
一个数可以向两边扩展,但不能逾越比它大的数。
设l[i]表示a[i]左边界,r[i]表示右边界。
设最终序列是b,如果bj==a[i], 那么l[i] < j < r[i]。

比如: a[] = {2, 3, 4, 1};
那么l[2] = 1, 因为a[1] < a[2];
r[2] = 4, 因为a[4] < a[2];

假设最终序列是b,那么b的前i个数可能有很多种情况。
定义dp[i][j]:b中长度为i的以a[j]结束的前缀序列有多少种。
要计算dp[i][j],需要利用dp[i -1][x],意思是,长度为i的前缀与长度为i-1的前缀是有关联的,如果长度为i的前缀以a[j]结尾, 那么x必须满足一些限制,否则dp[i][j]不可能以a[j]结尾。

要计算dp[i][j], 分为三种情况:
分别是j < i, j ==i, j > i;
下面仅对j < i进行讨论,其余两种情况分析方法一样。

设最终序列是b,当j < i时:
若r[j] <= i, dp[i][j]=0, 意思是a[j]无法出现在b[i]处;
否则, a[j]能够出现在b[i]处,
dp[i][j] = sum{ dp[i - 1][x] },其中 x < i && a[x] <= a[j]; 因为长度为i的前缀如果以a[j]结尾,那么这个长度为i的序列的前i - 1个数的最后一个数必须是小于等于a[j]的,小于a[j]的数可能在j左侧,也可能是在右侧,显然右侧是不可行的。
sum{dp[i - 1][x]}可以转化为sum{ dp[i - 1][y] }其中 y<=j,原因是如果x < i && a[x] < a[j],那么x < j。再把j之前的比a[j]大的那部分数加上(其在i-1这个位置出现的可能性是0,不影响结果),就变成了sum{ dp[i - 1][y] }

总的来说当j < i时, dp[i][j] = sum{ x < i && a[x] <= a[j] | dp[i - 1][x] } = sum{y <= j | dp[i - 1][y]} = dp[i - 1]的前j项之和。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = (int)3005;
const ll mod = (ll)1e9 + 7;
ll dp[N][N];
ll l[N], r[N];
ll prefix[N];
ll h[N];
int main() {
  ios::sync_with_stdio(!cin.tie(0));
  int n;
  cin >> n;
  for (int i = 1; i <= n; ++i) {
    cin >> h[i];
  }
  for (int i = 1; i <= n; ++i) {
    for (int j = i - 1; ; --j) {
      if (h[j] < h[i]) {
        l[i] = j;
        break;
      }
    }
    for (int j = i + 1; ; ++j) {
      if (h[j] < h[i]) {
        r[i] = j;
        break;
      }
    }
  }
  for (int j = 1; j <= n; ++j) {
    if (l[j] < 1) {
      dp[1][j] = 1;
    }
    prefix[j] = prefix[j - 1] + dp[1][j];
  }
  for (int i = 2; i <= n; ++i) {
    for (int j = 1; j <= n; ++j) {
      if (l[j] < i && i < r[j]) {
        dp[i][j] += prefix[j];
        dp[i][j] %= mod;
      }
      prefix[j] = (prefix[j - 1] + dp[i][j]) % mod;
    }
  }
  cout << accumulate(dp[n] + 1, dp[n] + n + 1, 0ll) % mod << endl;
  return 0;
}

对于样例的第三个
7
1 4 2 5 3 6 7

124

其dp[i][j]如图:横向为i, 纵向为j
答案是dp[n][1 … n];
1 + 7 + 26 + 45 + 45 = 124;
dp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值