ARC124E - Pass to Next——DP、容斥

E - Pass to Next

题目描述

n n n 个人排成一个,第 i i i 个人有 a i a_i ai 个球,每个人可以拿出其中一部分(可以是全部或0)给后面一个人,这个过程只发生一次,也就是说没有哪个球会传到后面第二个人去。设传完后每个人手里有 x i x_i xi 个球,求所有情况下 Π i = 1 n x i \Pi_{i=1}^nx_i Πi=1nxi 的总和。

数据范围与提示

3 ≤ N ≤ 1 0 5 , 0 ≤ a i ≤ 1 0 9 3\leq N\leq 10^5,0\le a_i\le 10^9 3N105,0ai109

前言

这题真的好难。难在你可以很熟练地通过情景列出式子,然而看见式子却不能联想到原情景。

思路

首先我们考虑什么情况下两种不同的传递方案可以得到相同的结果。显然在一种方案下,每个人开始时都多拿出一个球(如果可以的话),最后每个人手上的球数不变。也就是说,差分后相同的传递方案对应的是同一个结果。而两种差分后不同的传递方案显然对应着不同的结果,所以我们证明:差分后的传递方案与最终的序列一一对应。还有一种说法也是一一对应的,也就是钦定传递方案中的最小值为0。从这个角度,我们可以直接算出方案数为 Π i = 1 n ( a i + 1 ) − Π i = 1 n a i \Pi_{i=1}^n(a_i+1)-\Pi_{i=1}^na_i Πi=1n(ai+1)Πi=1nai

但是我们要求的并不是方案数,而是 Π i = 1 n x i \Pi_{i=1}^nx_i Πi=1nxi 这么一个奇怪的东西。这题的难点就在这里。这个式子其实就是传完球后,每个人再从手里 x i x_i xi 个不同的球中拿出一个的方案数。对此,我们可以简单地分类讨论,然后用DP解决它。

d p [ i ] [ j ] , ( j = 0 / 1 ) dp[i][j],(j=0/1) dp[i][j],(j=0/1) ,若 j = 0 j=0 j=0 ,表示第 i i i 个人从自己原本有的球中选球,前 i − 1 i-1 i1 个人选球的方案数;若 j = 1 j=1 j=1 ,表示第 i i i 个人从前面一个人给的球中选球,前 i i i 个人选球的方案数。为什么这样扭曲地设计,是因为不同来源的球之间的分配互相独立,前一个人传多少给下一个人与下一个人传出多少之间没有关系。因此我们讨论方案时,只能把来源相同的球一起考虑。下面是 2 × 2 2\times2 2×2 的分类讨论转移。

(设 s ( n ) = ∑ i = 1 n i = n ( n + 1 ) 2 , g ( n ) = ∑ i = 1 n i 2 = n ( n + 1 ) ( 2 n + 1 ) 6 s(n)=\sum_{i=1}^ni=\frac{n(n+1)}{2},g(n)=\sum_{i=1}^ni^2=\frac{n(n+1)(2n+1)}{6} s(n)=i=1ni=2n(n+1),g(n)=i=1ni2=6n(n+1)(2n+1)

  • d p [ i ] [ 0 ] → d p [ i + 1 ] [ 0 ] dp[i][0]\rightarrow dp[i+1][0] dp[i][0]dp[i+1][0] ,由于 d p [ i ] [ 0 ] dp[i][0] dp[i][0] 没算第 i i i 个人的选球方案,所以要在转移时把它加上。如果第 i i i 个人剩下 b b b 个球,就有 b b b 种拿球方案,所以总的方案数是 1 ∼ a i 1\sim a_i 1ai 的等差数列求和, d p [ i + 1 ] [ 0 ] + = d p [ i ] [ 0 ] ⋅ s ( a i ) dp[i+1][0]+=dp[i][0]\cdot s(a_i) dp[i+1][0]+=dp[i][0]s(ai)
  • d p [ i ] [ 1 ] → d p [ i + 1 ] [ 0 ] dp[i][1]\rightarrow dp[i+1][0] dp[i][1]dp[i+1][0] ,这时是算上了第 i i i 个人的选球方案,而且不需要算下一个人的,所以只需算上传球的方案数, d p [ i + 1 ] [ 0 ] + = d p [ i ] [ 1 ] ⋅ ( a i + 1 ) dp[i+1][0]+=dp[i][1]\cdot (a_i+1) dp[i+1][0]+=dp[i][1](ai+1)
  • d p [ i ] [ 0 ] → d p [ i + 1 ] [ 1 ] dp[i][0]\rightarrow dp[i+1][1] dp[i][0]dp[i+1][1] ,这个时候的转移需要算上第 i i i 个人和第 i + 1 i+1 i+1 个人的选球方案,而且第 i + 1 i+1 i+1 个人选的是第 i i i 个人的球。如果第 i i i 个人剩下 b b b 个球,第 i + 1 i+1 i+1 个人就拿到了 a i − b a_i-b aib 个球,方案数为 b ⋅ ( a i − b ) b\cdot (a_i-b) b(aib) ,总的方案数为 ∑ i = 1 a i i ⋅ ( a i − i ) = a i ⋅ s ( a i ) − g ( a i ) \sum_{i=1}^{a_i}i\cdot(a_i-i)=a_i\cdot s(a_i)-g(a_i) i=1aii(aii)=ais(ai)g(ai) ,所以 d p [ i + 1 ] [ 1 ] + = d p [ i ] [ 0 ] ⋅ ( 前 面 那 坨 ) dp[i+1][1]+=dp[i][0]\cdot(前面那坨) dp[i+1][1]+=dp[i][0]()
  • d p [ i ] [ 1 ] → d p [ i + 1 ] [ 1 ] dp[i][1]\rightarrow dp[i+1][1] dp[i][1]dp[i+1][1] ,这个时候只需要算上第 i + 1 i+1 i+1 个人的方案,而第 i + 1 i+1 i+1 个人选的是第 i i i 个人的球,这和第 i i i 个人自己选自己的球的方案数是一样的,即 d p [ i + 1 ] [ 1 ] + = d p [ i ] [ 1 ] ⋅ s ( a i ) dp[i+1][1]+=dp[i][1]\cdot s(a_i) dp[i+1][1]+=dp[i][1]s(ai)

由于转移是一个环,所以那需要先钦定第1个人是选自己的球还是选第 n n n 个人的球(初值设为1,另一个设为0),然后转移一圈。如果你钦定 d p [ 1 ] [ 0 ] = 1 , d p [ 1 ] [ 1 ] = 0 dp[1][0]=1,dp[1][1]=0 dp[1][0]=1,dp[1][1]=0 ,那么最终答案就是 d p [ 1 ] [ 0 ] − 1 dp[1][0]-1 dp[1][0]1 ,反过来是一样的。

然而这个DP没有考虑上面传球最小值为0的约束,所以要用一用容斥。和计算传球结果方案数一样的方法,先普通做一遍DP,然后把上面的转移稍微改改,使传球的数量不能为0,再做一遍DP。最后用第一次DP的结果减去第二次DP的结果就是答案。

时间复杂度是 O ( n ) O(n) O(n) 。当然,你还可以设计其它的DP来做,不过可能会复杂一点。

代码

#include<cstdio>//JZM yyds!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<ctime>
#include<map>
#define ll long long
#define MAXN 100005
#define uns unsigned
#define INF 1e17
#define MOD 998244353ll
#define lowbit(x) ((x)&(-(x)))
#define ull uns ll
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
inline ll ksm(ll a,ll b,ll mo){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}
const ll iv2=(MOD+1)>>1,iv3=ksm(3,MOD-2,MOD),iv6=iv2*iv3%MOD;
int n;
ll a[MAXN],dp[MAXN][2],ans1,ans2;
inline ll dodp(bool C,bool D){//C-钦定初值,D-传球数是否不能为0
	memset(dp,0,sizeof(dp));
	dp[1][0]=C,dp[1][1]=C^1;
	for(int i=1;i<=n;i++){
		int t=i%n+1;
		ll b=a[i]-D,c,e;
		ll d=b*(b+1)%MOD*iv2%MOD;
		dp[t][0]=(dp[t][0]+dp[i][0]*d%MOD)%MOD;
		dp[t][0]=(dp[t][0]+dp[i][1]*(b+1)%MOD)%MOD;
		if(D)b++;
		c=b*(b+1)%MOD*(b*2+1)%MOD*iv6%MOD;
		d=b*(b+1)%MOD*iv2%MOD,e=((d*b-c)%MOD+MOD)%MOD;
		dp[t][1]=(dp[t][1]+dp[i][0]*e%MOD)%MOD;
		dp[t][1]=(dp[t][1]+dp[i][1]*d%MOD)%MOD;
	}
	return ((C?dp[1][0]:dp[1][1])+MOD-1)%MOD;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	printf("%lld\n",((dodp(1,0)+dodp(0,0)-dodp(1,1)-dodp(0,1))%MOD+MOD)%MOD);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值