6439. 【GDOI2020模拟01.17】小 ω 数排列

题目

正解

一种很套路的笛卡尔树DP……
看着那个绝对值很烦,于是我们考虑一种全新的转移方式。
考虑把数字从小到大,一个一个插入当前序列的空隙中。
于是我们就可以知道这个数字对答案的贡献。
比如,如果当前它两边没有数字,那么系数就是 − 2 -2 2
如果一边有数字,系数就是 + 1 − 1 +1-1 +11也就是 0 0 0
如果两边都有数字,系数就是 2 2 2
当然,对于放在边界的数字要特殊判断。
于是就有了这样的DP:设 f i , j , k , t f_{i,j,k,t} fi,j,k,t表示前 i i i个数都放好了,形成 j j j段序列,当前的总贡献是 k k k,放了 t t t(取值为 0 , 1 , 2 0,1,2 0,1,2)个在边界上的·数字。
讨论一下这个数要插入到哪里,有没有跟相邻的区间相连。这样就可以搞出一个DP。
写出所有方程式,这时候会发现 k k k的取值范围比较奇怪,必定存在着许多的冗余状态。
设一个 k k k的替代品 k ′ k' k k ′ = k + ( 2 j − t ) a i k'=k+(2j-t)a_i k=k+(2jt)ai
相当于我们想象一下在所有的空位填上一个 a i a_i ai得到的总贡献。
有这个总贡献的定义得知, k ′ k' k是在一个比较正常的取值范围之内的。
枚举的时候枚举 k ′ k' k,再转化成 k k k,存储的时候转化成 k ′ k' k
这样就可以保证时间复杂度了。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 110
#define maxL 1010
#define mo 1000000007
#define ll long long
int n,L;
int a[N];
int f[N][N][maxL][3];
void upd(int i,int j,int k,int t,int val){
//	printf("f(%d,%d,%d,%d)+=%d\n",i,j,k,t,val);
	k=k+(2*j-t)*a[i];
	if (k<=L)
		(f[i][j][k][t]+=val)%=mo;
}
int main(){
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d%d",&n,&L);
	if (n==1){
		printf("1\n");
		return 0;
	}
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	memset(f,0,sizeof f);
	f[0][0][0][0]=1;
	ll ans=0;
	for (int i=0;i<n;++i){
		for (int j=0;j<=i;++j)
			for (int k=0;k<=L;++k){
				for (int t=0;t<=2;++t){
					int tmp=f[i][j][k][t];
					if (!tmp)
						continue;
					int rk=k-(2*j-t)*a[i];
//					printf("\nf(%d,%d,%d,%d)=%d\n",i,j,rk,t,f[i][j][k][t]);
//					if (i==2 && j==2 && rk==-3 && t==2)
//						printf("");
					upd(i+1,j+1,rk-2*a[i+1],t,(ll)tmp*(j+1-t)%mo);
					if (j)
						upd(i+1,j,rk,t,(ll)tmp*(2*j-t)%mo);	
					if (t<2){
						upd(i+1,j+1,rk-a[i+1],t+1,(ll)tmp*(2-t)%mo);
						if (j)
							upd(i+1,j,rk+a[i+1],t+1,(ll)tmp*(2-t)%mo);
					}
					if (j>=2)
						upd(i+1,j-1,rk+2*a[i+1],t,(ll)tmp*(j-1)%mo);
				}
			}
	}
//	printf("\n");
	for (int k=0;k<=L;++k){
//		printf("f(%d,%d,%d,%d)=%d\n",n,1,k,2,f[n][1][k][2]);
		ans+=f[n][1][k][2];
	}
	printf("%lld\n",ans%mo);
	return 0;
}

总结

笛卡尔树DP,也就是按顺序枚举插入,在转移过程中合并连续段。这是一个大套路啊……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值