DTOJ#5239. 历史

传送门
在历史上,有这样一个国家,这个国家由 n n n 个村庄组成。第 i i i 个村庄有 a i a_i ai 个人。由于每个村庄都实行严格的计划生育,在整个历史的过程中,每个村庄的人数都没有变化。

n n n 个村庄线性排列在一条线上,并且只有相邻的村庄能互通。也就是说,对于所有的 1 ≤ i < n 1 \le i<n 1i<n ,村庄 i i i i + 1 i+1 i+1 存在一条边。除了这些边外,其他村庄之间不能同行。换句话说,对于 i < j i<j i<j ,如果想要从 i i i 走到 j j j ,那么必须经过 i , i + 1 ⋯   , j − 1 , j i,i+1\cdots, j-1,j i,i+1,j1,j 这些村庄。

村庄之间经常会闹矛盾,小矛盾可能不久后会解决,但是大矛盾就危险了,如果村庄 i , i + 1 i,i+1 i,i+1 之间闹大矛盾,那么这两个村庄就会断绝来往,并且破坏之间
的道路。这样一来,村庄之间就不连通了。

一个国家一旦不连通是非常致命的,所以这个国家的领导者会放弃这个国家(???),并且在剩下的两个连通块中选择村庄数较多的一个,把它作为自己的新国家。如果左右两个连通块的村庄数相同,那么他会选择右边的(村庄编号大的)连通块。

当然,另一个被放弃的连通块也不会消失,他们会举行一次选举,选出新的领导者。这次选举每个人都有选举权,所以每个人都有一张选票。因此,这次选举需要制作连通块内村庄人数之和的选票。在经过这样的操作后,一个国家就会分裂成两个国家了。然而,村庄之间的矛盾并不会消除,只要两个村庄还在一个国家中,之后还
会有村庄之间闹大矛盾。因此,这些村庄还会变成三个,四个,直到最后变成 n n n个国家。

不过值得庆幸的是,发生大矛盾的概率很低,所以在历史的长河中,需要隔很久才会发生一次国家的分裂,并且每条边分裂的概率相等。

尽管如此,最后这些村庄还是分裂成了 n n n 个国家。

“听完这个故事,你有什么感想? ”小 H 说。

“这个故事有任何寓意吗?不对,这是个故事吗? ”宫水三叶说。

“是吗?那我问你,从一个国家到 n n n 个国家,期望制作了多少张选票?当然,

我问的是对 998244353 998244353 998244353 取模后的值。 ”小 H 说。

给定一个长度为 n n n 的链,第 i i i 个点的点权为 a i a_i ai ,其中对于所有的 1 ≤ i < n 1 \le i<n 1i<n ,点 i i i 和点 i + 1 i + 1 i+1 相连。总共有 n − 1 n − 1 n1 次操作,每次操作等概率会选择一条还未断开的边,然后把它断开。此次操作的权值为剩下两部分中点数较少的部分的点权之和,如果两部分点数相等,权值为点编号较小
的那部分的点权之和

n − 1 n − 1 n1 次操作后权值之和的期望值对 998244353 998244353 998244353 取模后的结果。

第一行一个整数 n n n

第二行 n n n 个整数,第 i i i 个数表示 a i a_i ai ,即第 i i i 个村庄的人数。

输出一行,表示期望的选票数量对 998244353 998244353 998244353 取模后的结果。

样例输入1
3
1 2 3
样例输出1
499122180
样例解释1

如果先断开 (1,2) ,再断开 (2,3) ,那么权值为 1 + 2 = 3。
如果先断开 (2,3) ,再断开 (1,2) ,那么权值为 3 + 1 = 4。
因此期望值为 3 + 4 2 = 7 2 \frac{3+4}{2}=\frac{7}{2} 23+4=27

对于所有数据,满足 1 ≤ n ≤ 2 × 1 0 6 , 0 ≤ s i ≤ 1 0 9 1 \le n \le 2 \times 10^6, 0 \le s_i \le 10^9 1n2×106,0si109

子任务编号 n n n特殊性质分值
1$ \le 10$-5
2$ \le 20$-5
3$ \le 3000$-20
4$ \le 2000$-20
5$ \le 2000$ S i = 1 S_i=1 Si=110
6$ \le 5 \times 10^4$ S i = 1 S_i=1 Si=110
7$ \le 2 \times 10^6$ S i = 1 S_i=1 Si=110
8$ \le 2 \times 10^6$-20

题解:
首先,一眼 D P DP DP
首先想到区间 D P DP DP。转移显然。
n 3 n^3 n3 也显然过不了。
这时发现部分分 s i = 1 s_i=1 si=1,发现同样长的区间必然相同。
于是我们直接 f [ i ] f[i] f[i] 表示长度为 i i i 的区间的答案。
转移即 f [ i ] ( i − 1 ) ! = 2 ( ∑ j f [ j ] j ! ) + 2 ( ∑ j ⌊ i − 1 2 ⌋ j ) − ( ( i + 1 ) / 2 [ 2 ∣ i + 1 ] ) \frac{f[i]}{(i-1)!}=2(\sum_j \frac{f[j]}{j!})+2(\sum_j^{\left\lfloor\dfrac{i-1}{2}\right\rfloor}j)-((i+1)/2[2|i+1]) (i1)!f[i]=2(jj!f[j])+2(j2i1j)((i+1)/2[2i+1])
前缀和优化即可。

#include<bits/stdc++.h>
#define N 2000006
typedef long long ll;
using namespace std;
const ll mod=998244353;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
ll jc[N],jcinv[N];
inline ll power(ll x,int c){
	ll now=1;
	while(c){
		if(c&1)now=now*x%mod;
		x=x*x%mod;c>>=1;
	}
	return now;
}
ll f[N],sum[N],a[N];
inline void init(int n){
    jc[0]=1;
	for(int i=1;i<=n;++i)jc[i]=jc[i-1]*1ll*i%mod,sum[i]=(sum[i-1]+i)%mod;
	jcinv[n]=power(jc[n],mod-2);
	for(int i=n-1;i+1;--i)jcinv[i]=jcinv[i+1]*1ll*(i+1)%mod;
	ll sumf=0;
	for(int i=1;i<=n;++i){
		f[i]=2*sumf+2*sum[(i-1)/2+1];
		if((i+1)%2==0)f[i]-=(i+1)/2;
		f[i]=f[i]%mod*jc[i-1]%mod;
		sumf=(sumf+f[i]*jcinv[i]%mod)%mod;
	}
}
ll g[305][305],suma[N];
inline ll calc(int l,int r,int x,int y){
	if(r-l+1<=y-x+1)return suma[r]-suma[l-1];
	else return suma[y]-suma[x-1];
}
int main(){
	int n=read(),flag=1;
	for(int i=1;i<=n;++i)a[i]=read(),flag&=(a[i]==1);
	for(int i=1;i<=n;++i)suma[i]=suma[i-1]+a[i];
	init(n);
	if(flag){
		ll ans=f[n-1]*jcinv[n-1]%mod;
		printf("%lld\n",(ans+mod)%mod);
		return 0;
	}
	for(int len=2;len<=n;++len){
		for(int i=1;i<=n-len+1;++i){
			int j=i+len-1;
			for(int k=i;k<j;++k){
				g[i][j]=(g[i][j]+g[i][k]+g[k+1][j]+calc(i,k,k+1,j)%mod)%mod;
			}
			g[i][j]=g[i][j]*jcinv[j-i]%mod*jc[j-i-1]%mod;
		}
	}
	//for(int i=1;i<=n;++i)cout<<jcinv[i]<<" ";cout<<endl;
	ll ans=g[1][n];
	printf("%lld\n",(ans+mod)%mod);
	return 0;
}
/*
10
5 9 8 6 3 2 1 4 7 10

*/

但是很明显 D P DP DP 没有前途。
所以考虑计数。
发现对于一段区间 [ L , R ] [L,R] [L,R],设 l e n len len 为长度。假设作为左区间,那么只需要先删 [ L − 1 , L ] [L-1,L] [L1,L]的边,再删 [ R , R + 1 ] [R,R+1] [R,R+1],然后删 [ L , R ] [L,R] [L,R] [ R + 1 , R + 1 + l e n ] [R+1,R+1+len] [R+1,R+1+len],其他边任意就可以,于是系数为 C ( n − 1 , 2 ∗ l e n + 2 ) × ( 2 ∗ l e n ) ! × ( n − 1 − ( 2 ∗ l e n − 2 ) ) ! C(n-1,2*len+2)\times (2*len)! \times (n-1-(2*len-2))! C(n1,2len+2)×(2len)!×(n1(2len2))!
时间复杂度 O ( n 2 ) O(n^2) O(n2)

#include<bits/stdc++.h>
#define N 2000006
typedef long long ll;
using namespace std;
const ll mod=998244353;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
ll jc[N],jcinv[N];
inline ll power(ll x,int c){
	ll now=1;
	while(c){
		if(c&1)now=now*x%mod;
		x=x*x%mod;c>>=1;
	}
	return now;
}
inline void init(int n){
    jc[0]=1;
	for(int i=1;i<=n;++i)jc[i]=jc[i-1]*1ll*i%mod;
	jcinv[n]=power(jc[n],mod-2);
	for(int i=n-1;i+1;--i)jcinv[i]=jcinv[i+1]*1ll*(i+1)%mod;
}
inline ll C(int x,int y){
	if(x<y||y<0)return 0;
	return jc[x]*jcinv[y]%mod*jcinv[x-y]%mod;
}
ll sum[N],a[N];
int main(){
	int n=read();
	for(int i=1;i<=n;++i)a[i]=read(),sum[i]=(sum[i-1]+a[i])%mod;
	init(n+3);
	ll ans=0;
	for(int i=1;i<=n;++i){
		for(int j=i;j<=n;++j){
			int len=j-i;ll now;
			if(j+1+len>n)continue;
			if(i==1){
				now=C(n-1,2*len+1)*jc[2*len]%mod*jc[n-1-2*len-1]%mod;
			}else{
				now=C(n-1,2*len+2)*jc[2*len]%mod*jc[n-1-2*len-2]%mod;
			}
			ans=(ans+now*(sum[j]-sum[i-1])%mod)%mod;
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=i;j<=n;++j){
			int len=j-i;ll now;
			if(i-2-len<1)continue;
			if(j==n){
				now=C(n-1,2*len+2)*jc[2*len+1]%mod*jc[n-1-2*len-2]%mod;
			}else{
				now=C(n-1,2*len+3)*jc[2*len+1]%mod*jc[n-1-2*len-3]%mod;
			}
			ans=(ans+now*(sum[j]-sum[i-1])%mod)%mod;
		}
	}
	ans=ans*jcinv[n-1]%mod;
	printf("%lld\n",(ans+mod)%mod);
	return 0;
}

又发现同样长度的区间系数相同,那么只需要对 a i a_i ai 的前缀和 s u m i sum_i sumi 再取前缀和就可以维护了。
注意分类 [ 1 , R ] [1,R] [1,R] 的情况,系数不一样。
右区间同理。
时间复杂度 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
#define N 2000006
typedef long long ll;
using namespace std;
const ll mod=998244353;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
ll jc[N],jcinv[N];
inline ll power(ll x,int c){
	ll now=1;
	while(c){
		if(c&1)now=now*x%mod;
		x=x*x%mod;c>>=1;
	}
	return now;
}
inline void init(int n){
    jc[0]=1;
	for(int i=1;i<=n;++i)jc[i]=jc[i-1]*1ll*i%mod;
	jcinv[n]=power(jc[n],mod-2);
	for(int i=n-1;i+1;--i)jcinv[i]=jcinv[i+1]*1ll*(i+1)%mod;
}
inline ll C(int x,int y){
	if(x<y||y<0)return 0;
	return jc[x]*jcinv[y]%mod*jcinv[x-y]%mod;
}
ll sum[N],a[N],tt[N],ccl[N];
int main(){
	int n=read();
	for(int i=1;i<=n;++i)a[i]=read(),sum[i]=(sum[i-1]+a[i])%mod,tt[i]=(sum[i]+tt[i-1])%mod;
	init(n+3);
	ll ans=0;
	for(int len=0;len<=(n-2)/2;++len){
		ll pre=ans,now;
		now=((tt[n-len-1]-tt[2+len-1])-(tt[n-2*len-1-1]-tt[1-1]))%mod*C(n-1,2*len+2)%mod*jc[2*len]%mod*jc[n-1-2*len-2]%mod;
		ans=(ans+now)%mod;
		now=C(n-1,2*len+1)*jc[2*len]%mod*jc[n-1-2*len-1]%mod*(sum[1+len]-sum[0])%mod;
		ans=(ans+now)%mod;
	}
	ans=(ans+mod)%mod;
	for(int len=0;len<=(n-3)/2;++len){
		ll pre=ans,now;
		now=((tt[n-1]-tt[2*len+3-1])-(tt[n-1-len-1]-tt[len+3-1-1]))%mod*C(n-1,2*len+3)%mod*jc[2*len+1]%mod*jc[n-1-2*len-3]%mod;
		ans=(ans+now)%mod;
		now=C(n-1,2*len+2)*jc[2*len+1]%mod*jc[n-1-2*len-2]%mod*(sum[n]-sum[n-len-1])%mod;
	    ans=(ans+now)%mod;
	}
	ans=(ans+mod)%mod;
	ans=ans*jcinv[n-1]%mod;
	printf("%lld\n",(ans+mod)%mod);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值