2020 CCPC-Wannafly Winter Camp Day6 (Div.1&2) D. 递增递增(区间dp/填坑dp)

题目

n(2<=n<=50)个区间,第i个区间[li,ri](0<=li<ri<=2^60),

你可以从第i个区间里选取一个数放在新序列的第i位,所构成一个长度为n的新序列

若新序列是非严格单调递增序列,则将其所有元素的和加到答案里,求最终的答案。

思路来源

https://blog.csdn.net/qq_43202683/article/details/104069286

https://blog.csdn.net/Code92007/article/details/104120403(edu Round81 F题)

题解

wls口中的填坑dp,是一种比较新的题型,补了一下eduRound的F又回来补了一下这题

其实不知道该归到哪一类,貌似是区间dp和组合数学的范畴

dp[i][j]表示当前选了i个数,所有的数都小于等于第j个区间的方案数

sum[i][j]表示当前选了i个数,所有的数都小于等于第j个区间的合法方案的和

dp[i][j]的方案数推导详见edu Round81 F题(思路来源第二篇),一模一样的,就不细说了

 

考虑从[l,r]内多选一个数对sum的和的贡献,是期望意义的(l+r)/2,

一个数的时候好理解,多个数之间为什么加一个还是(l+r)/2,不会影响既得方案,

具体证明大概可以口胡……

比如,111 112 113 122 123 133 222 223 233 333,其平均数(ave)是2,

那么,对于一个非严格单增序列(a,b,c),

一定可以找到其对应的非严格单增序列(2*ave-c,2*ave-b,2*ave-a),两两配对之后的平均数是ave,

自耦的情况,(a,b,c)=(c,b,a),本身不用配对也成立,故所有情况均成立

 

sum[i][j]=((sum[i][j]+1ll*sum[k-1][j-1]*C%mod)%mod+1ll*dp[k-1][j-1]*C%mod*ave%mod*num%mod)%mod;

当前区间合法方案序列的和=前一区间合法方案序列的和*当前区间有多少种方案能续在前一区间的和上+本区间内额外增加的序列的和*本区间内得到这些和有多少种选法*之前有多少种方案可以往后续序列

其中,本区间内额外增加的序列的和,比如选y个,每个贡献是(l+r)/2,则该和=y*(l+r)/2

注意取模的细节问题,不然可能爆long long

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=52,M=4*N,mod=998244353; 
//dp[i][j]代表当前选了i个数 所有的数都选自离散化后小于等于第j个区间的方案数 
int n,cnt;
ll inv2,x[M],dp[N][M],sum[N][M],l[N],r[N];
ll modpow(ll x,ll n)
{
	ll res=1;
	for(;n;n>>=1,x=1ll*x*x%mod)
	if(n&1)res=1ll*res*x%mod;
	return res;
}
ll inv(ll x)
{
	return modpow(x,mod-2);
}
int main()
{
	inv2=inv(2);
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%lld",&l[i]);
		x[++cnt]=l[i];
	}
	for(int i=1;i<=n;++i)
	{
		scanf("%lld",&r[i]);r[i]++;
		x[++cnt]=r[i];
	}
	sort(x+1,x+cnt+1);
	cnt=unique(x+1,x+cnt+1)-(x+1);
	for(int i=1;i<=n;++i)
	{
		l[i]=lower_bound(x+1,x+cnt+1,l[i])-x;
		r[i]=lower_bound(x+1,x+cnt+1,r[i])-x;
	}
	for(int j=0;j<=cnt;++j)
	{
		dp[0][j]=1;
		sum[0][j]=0;
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=l[i];j<r[i];++j)
		{
			ll C=1;
			for(int k=i;k;--k)
			{
				if(!(l[k]<=j&&j<r[k]))break;
				ll num=i-k+1;
				ll range=(x[j+1]-x[j])%mod;//标号 区间对应左端点 第j个区间的范围[x[j],x[j+1])
				ll ave=(x[j+1]-1+x[j])%mod*inv2%mod;
				C=1ll*C*(range+num-1)%mod*inv(num)%mod; 
				dp[i][j]=(dp[i][j]+1ll*dp[k-1][j-1]*C%mod)%mod;//[k,i]这一段都选第j个区间的值构成增序列 
				sum[i][j]=((sum[i][j]+1ll*sum[k-1][j-1]*C%mod)%mod+1ll*dp[k-1][j-1]*C%mod*ave%mod*num%mod)%mod;
				//相当于range个数中选num个构成非严格增序列(即组合可重问题) 
				//答案是C(range+num-1,num) 注意到num不断+1 
				//C(n,k)=C(n-1,k-1)*n/k  
			}
		}
		for(int j=1;j<=cnt;++j)
		{
			dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
			sum[i][j]=(sum[i][j]+sum[i][j-1])%mod;
		}
	}
	printf("%lld\n",sum[n][cnt]);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值