P9197 [JOI Open 2016] 摩天大楼 / Skyscraper

#include<bits/stdc++.h>
using namespace std;

using ll=long long;
const int N=101,mod=1e9+7;
int n,l;
ll a[N],dp[N][N][1001][3];
//dp[i][j][k][t]:前i个数中,有j个连续段(这是连续段DP中常用的概念,此处不多叙述),总和为k,有t个碰到边界

int main(){
	cin.tie(nullptr)->sync_with_stdio(false);
	cin>>n>>l;
	for(int i=1;i<=n;i++) cin>>a[i];
	
	if(n==1){
		//特判
		cout<<1<<'\n';
		return 0;
	}
	
	sort(a+1,a+1+n);//排序去掉绝对值
	dp[0][0][0][0]=1;//初始化
	for(int i=0;i<n;i++)
		for(int j=0;j<=n;j++)
			for(int k=0;k<=l;k++)
				for(int t=0;t<=2;t++){
					ll val=(2*j-t)*(a[i+1]-a[i]);
					//计算插入这个数对于总和的贡献:当前有j个连续段,每个段有两个端点,扣除t个后,共2j-t个端点可累加差
					//这意味着它们最终必须和某个“更大的数”(从小到大插入a[i+1])相连,即对于每个端点,最后都会“连上”一个>=a[i+1]的数,对和的贡献必然有且仅有一个a[i+1]-a[i]
					/*
					如:
					段1: a_1 — a_3      段2: a_2
					↑     ↑           ↑   ↑
					端点   端点        端点 端点
					(没有点在端点上)
					共 2*j-t=4 个悬空端点。
					
					现在插入 a4,贡献为a4−a3。
					
					这 4 个端点未来都要连向 ≥a4 的数,每条边都跨过(a3,a4]。所以这一步贡献:4*(a4-a3)
					(假设a2最后右边与a6相连,则对和的贡献相当于a6-a2,即(a6-a5)+(a5-a4)+(a4-a3))
					*/
					if(!dp[i][j][k][t] || val+k>l || 2*j<t) continue;
					//接下来分类讨论对方案的贡献
					dp[i+1][j][k+val][t]=(dp[i+1][j][k+val][t]+dp[i][j][k][t]*(2*j-t)%mod)%mod;//接在某个段的端点(不碰边界)
					if(t<2) dp[i+1][j][k+val][t+1]=(dp[i+1][j][k+val][t+1]+dp[i][j][k][t]*(2-t)%mod)%mod;//接在某个段的端点(碰边界)
					if(j>0) dp[i+1][j-1][k+val][t]=(dp[i+1][j-1][k+val][t]+dp[i][j][k][t]*(j-1)%mod)%mod;//将两个段合并
					dp[i+1][j+1][k+val][t]=(dp[i+1][j+1][k+val][t]+dp[i][j][k][t]*(j+1-t))%mod;//单独成段,不碰边界
					if(t<2) dp[i+1][j+1][k+val][t+1]=(dp[i+1][j+1][k+val][t+1]+dp[i][j][k][t]*(2-t)%mod)%mod;//单独成段,在边界
				}
	
	ll ans=0;
	for(int i=0;i<=l;i++) (ans+=dp[n][1][i][2])%=mod;//累加答案
	cout<<ans<<'\n';
	return 0;
}

posted on 2026-10-22 20:44  _CENSORED  阅读(4)  评论(0)    收藏  举报

导航