【ARC104F】Visibility Sequence(区间DP)

Visibility Sequence

题目链接:ARC104F

题目大意

有一个数组 H,表示你构造的数组 X 中每一个位置的值上界,下界都是 1。
通过 X 构造数组 P,表示左边最后一个大于它的数的下标,如果没有就是 -1。
问你能构造出多少种不同的 P。

思路

发现数组长度只有 100 100 100,但是值域能到 1 e 5 1e5 1e5
但是完全没有必要,因为你只是大小关系,你完全可以把超过数组长度的设成数组长度,顶多就是它们两两之间都不一样嘛。

然后你发现这个 P P P 有点像一棵树,每次 i i i 指向 P i P_i Pi,最后会形成以 − 1 -1 1 为根的一棵树。
那问的就是这棵树能有多少形态呗。

而且边之间你放到区间里面是不会有交叉的,因为如果有 x , y , z , w x,y,z,w x,y,z,w 使得 z → x , w → y z\rightarrow x,w\rightarrow y zx,wy,那 x > z , y > w , w ⩾ z x>z,y>w,w\geqslant z x>z,y>w,wz
y > w ⩾ z y>w\geqslant z y>wz y > z y>z y>z,那不应该 z → y z\rightarrow y zy 嘛。
所以是不交叉的。

那每个子树都是一个区间,你其实可以试着区间 DP。
然后你安排值,为了给后面留位置你肯定是能放多小放多小。
f l , r , k f_{l,r,k} fl,r,k 表示一棵树 l , r l,r l,r 区间,里面最大值是 k k k
g l , r , k g_{l,r,k} gl,r,k 则表示森林的答案。

f l , r , k = g l + 1 , r , k − 1 f_{l,r,k}=g_{l+1,r,k-1} fl,r,k=gl+1,r,k1 放一个根连着儿子。
g l , r , k = ∑ m i d = l r − 1 ∑ v = 1 k f l , m i d , v g m i d + 1 , r , k + ∑ m i d = l r − 1 [ a m i d + 1 ⩾ k ] ∑ v = 1 k − 1 f l , m i d , k g m i d + 1 , r , v g_{l,r,k}=\sum\limits_{mid=l}^{r-1}\sum\limits_{v=1}^kf_{l,mid,v}g_{mid+1,r,k}+\sum\limits_{mid=l}^{r-1}[a_{mid+1}\geqslant k]\sum\limits_{v=1}^{k-1}f_{l,mid,k}g_{mid+1,r,v} gl,r,k=mid=lr1v=1kfl,mid,vgmid+1,r,k+mid=lr1[amid+1k]v=1k1fl,mid,kgmid+1,r,v
一种是直接放进去,一种是抬高了后面再放进去。


还有一种区间 DP 法。
前面我们从小到大考虑,我们考虑从大到小。
f l , r , k f_{l,r,k} fl,r,k 表示 l , r l,r l,r 区间来处理,除了自己本身的限制还有值域的全部上界 k k k

然后考虑从右往左枚举子树,因为子树之间那个儿子的值是要递增的。
那枚举最右边的那个子树的那个儿子的编号 m i d mid mid,那右边 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 的值域就是 k − 1 k-1 k1,左边的就是 k k k
两边方案乘起来,所以分的情况加起来即可。

代码

sol1

#include<cstdio>
#include<iostream>
#define ll long long
#define mo 1000000007

using namespace std;

const int N = 105;
int n, a[N];
ll f[N][N][N], g[N][N][N], fs[N][N][N], gs[N][N][N];

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]); a[i] = min(n, a[i]);
	}
	
	for (int len = 1; len <= n; len++)
		for (int l = 1; l + len - 1 <= n; l++) {
			int r = l + len - 1;
			if (l == r) {
				f[l][r][1] = g[l][r][1] = 1;
				for (int i = 1; i <= n; i++) fs[l][r][i] = gs[l][r][i] = 1;
				continue;
			}
			for (int i = 2; i <= a[l]; i++) f[l][r][i] = g[l + 1][r][i - 1];
			for (int i = 1; i <= n; i++) {
				g[l][r][i] = f[l][r][i];
				for (int mid = l; mid < r; mid++) {
					(g[l][r][i] += gs[l][mid][i] * f[mid + 1][r][i] % mo) %= mo;
					if (i <= a[mid + 1]) (g[l][r][i] += g[l][mid][i] * fs[mid + 1][r][i - 1] % mo) %= mo;
				}
			}
			for (int i = 1; i <= n; i++) fs[l][r][i] = (fs[l][r][i - 1] + f[l][r][i]) % mo, gs[l][r][i] = (gs[l][r][i - 1] + g[l][r][i]) % mo;
		}
	
	ll ans = 0;
	for (int i = 1; i <= n; i++) (ans += g[1][n][i]) %= mo;
	printf("%lld", ans);
	
	return 0;
}

sol2

#include<cstdio>
#include<iostream>
#define ll long long
#define mo 1000000007

using namespace std;

const int N = 105;
int n, a[N];
ll rem[N][N][N];

ll slove(int l, int r, int k) {
	if (l > r) return 1;
	if (!k) return 0;
	if (l == r ) return 1;
	if (rem[l][r][k] != -1) return rem[l][r][k];
	rem[l][r][k] = 0;
	for (int mid = l; mid <= r; mid++) {
		int val = min(k, a[mid]);
		(rem[l][r][k] += slove(l, mid - 1, val) * slove(mid + 1, r, val - 1) % mo) %= mo;
	}
	return rem[l][r][k];
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	
	for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) for (int k = 1; k <= n; k++) rem[i][j][k] = -1;
	printf("%lld", slove(1, n, n));
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值