[HDU6848]Expectation

题目

传送门

补一个数据范围:

测试点编号 $n\le $
1 \(1\)
2 \(2\)
3 \(5\)
4 \(10\)
5 \(50\)
6 \(100\)
7 \(300\)
8 \(500\)
9 \(1000\)
10 \(3000\)

题解

必备芝士1
必备芝士2
我也不知道哪个才是真正必备的

呐,话说,测试点 1,2,3 不是可以手算吗?

小球进洞模型 虽然我没见过就是了

其实这道题和期望没什么关系,设 \(S\) 为所有方案的移动总距离,显然最后要求的就是

\[\frac{S}{n!2^n}\bmod 998244353 \]

我们只要求出 \(S\),最后乘上 \(n!2^n\) 的逆就可以了.

现在难点在于求 \(S\),这个时候各种方法就大放光彩了......


算法零

人工组合数计算 \(n=1,2,5\)\(2n\) 条边分别被经过好多次,然后直接打表输出,时间复杂度 \(\mathcal O(1)\),期望得分 \(30pts\) 这不是爆踩 std ?

你已经为你的代码做了太多事


算法一

所谓算法竞赛,就是拼谁暴力打得好,有 \(n!2^n\) 个方案?那我全部枚举出来就是了...

时间复杂度 \(\mathcal O(n!2^n)\),期望得分 \(30pts\). 这也是我上交的代码

为什么题解写这个算法只有 \(20pts\)...... 手算都比这高


算法二

在算法一的基础上,将暴力改成状压,定义 \(f[i][s1][s2]\) 表示现在选择第 \(i\) 个球,可选的球状态为 \(s1\),可选的洞状态为 \(s2\).

不难发现状态有 \(n\times 2^n\times 2^{n+1}\) 个,时间复杂度也就是 \(\mathcal O(2^n\times 2^{n+1}\times n)=\mathcal O(2^{2n+1}\times n)\)或许有更好的写法,时间复杂度更低?

期望得分 \(40pts\).


算法三和四的正规版本来源于知名博主 C202044zxy,这里是传送门.

下面是个人 yy,可能会有细节问题,意会即可......


算法三

记第 \(i\) 个洞左边的球编号为 \(i\),往下挖掘性质:发现将 \([l,r]\) 的洞填满,要用到的球只有可能在 \([l,r+1]\),洞有 \(r-l+1\) 个,球有 \(r-l+2\) 个,必定会剩下一个球 \(k\),这个 \(k\) 相当于将整个区间 \([l,r]\) 分成两个部分:\([l,k-1]\)\([k+1,r]\) 两个区间内部自行解决.

我们可以考虑区间 \(DP\),状态显然,定义 \(f[l][r]\) 表示将 \([l,r]\) 的行星撞完的所有方案移动距离和,考虑转移,我们就枚举剩下的球是哪个

\[f[l][r]=\sum_{k=l}^{r}2\times (f[l][k-1]+f[k+1][r])+dis(l,k)+dis(k,r) \]

最后,有 \(S=f[1][n]\),同样还要乘上逆元.

时间复杂度 \(\mathcal O(n^3)\),期望得分 \(80pts\).


算法四

对于算法三,我们展开转移方程之后发现可以使用前缀和优化,将转移降至 \(\mathcal O(1)\),时间复杂度为 \(\mathcal O(n^2)\),期望得分 \(100pts\).


算法五

和算法三、四利用的性质不一样,我们得挖掘另外的性质.

我们考虑有 \(n\) 个球的情况,这个时候一共有 \(2n+1\) 个点,\(2n\) 个区间,我们不妨画一个简易的图出来:

这个图中,\(n=3\).

其中圆的是球,方形是洞,下面的线段表示的是区间,我们随便选一个球出来,让它随便进一个洞,就变成这样:

一个球和一个洞抵消掉了,而这个抵消操作,实际上就是选择一条边,将这条边连接的球和洞抵消,上述情况中我们选择的是第 \(3\) 条边.

这个时候,我们的场面上还剩下 \(2\) 个球,\(3\) 个洞,这是一个有 \(4\) 条边的子局面,为什么边变少了,因为这三条边变成同一条了:

我们发现,原来的第 \(2,3,4\) 条边全部变成子局面中的第 \(2\) 条边.

而这个子结构中,如果我们要统计原来的第 \(3\) 条边被经过了多少次,实际上就是统计在两个球下的第 \(2\) 条边被经过了多少次,不妨设 \(f[i][j]\) 为有 \(i\) 个球时,第 \(j\) 条边被走了多少次,那么上述就是

\[f[3][3]=f[2][2]+... \]

我们考虑完善这个转移方程,如果我们选择的不是第 \(3\) 条边,而是其他的边,比如说第 \(2\) 条?我们发现这个时候第三条边变成子状态的第一条边.

我们再尝试一下,如果选择第 \(4\) 条边,第三条边在子状态中还是第三条边,而选择第 \(5,6\) 条边效果是一样的.

我们完善这个方程,有

\[f[3][3]=f[2][1]+f[2][2]+3\times f[2][3] \]

总结上述规律:对于 \(i\) 个球下的第 \(j\) 条边:

  1. 选择 \([1,j-1]\) 中的某条边,\((i,j)\rightarrow(i-1,j-2)\)
  2. 选择 \(j\)\((i,j)\rightarrow (i-1,j-1)\)
  3. 选择 \([j+1,2i]\)\((i,j)\rightarrow (i-1,j)\)

转移方程为

\[f_{i,j}=[j\ge 2]\times f_{i-1,j-2}\times (j-1)+f_{i-1,j-1}+f_{i-1,j}\times (2i-j) \]

初始化 \(f_{1,1}=f_{1,2}=1\),这个类似于预处理,复杂度 \(\mathcal O(n^2)\),然后,我们再来个 \(\mathcal O(n)\) 线性求 \(S\) 即可.

但是,这样会

为什么?我们所谓的“选择”,统计的是什么?上面的转移统计的全部都是在先合并其他球和边的方案中,经过 \(j\) 的情况,而我们并没有统计首先就选择 \(j\) 的方案,所以需要加上 \((i-1)2^{i-1}\).

所以转移方程就是

\[f_{i,j}=[j\ge 2]\times f_{i-1,j-2}\times (j-1)+f_{i-1,j-1}+f_{i-1,j}\times (2i-j)+(i-1)2^{i-1} \]

时间复杂度 \(\mathcal O(n^2+n)\).


\(p.s.\) 这道题在 \(HDU\) 上好像还有一个 \(T\le 2000\),对于算法三、四似乎过不去...

#include<iostream>
using namespace std;

namespace IO{
	#define rep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i<=i##_end_;++i)
	#define fep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i>=i##_end_;--i)
	#define low_rep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i<i##_end_;++i)
	#define upp_fep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i>=i##_end_;--i)
	#define erep(i,u) for(signed i=tail[u],v=e[i].to;i;i=e[i].nxt,v=e[i].to)
	#define writc(a,b) fwrit(a),putchar(b)
	#define mp(a,b) make_pair(a,b)
	#define fi first
	#define se second
	typedef long long ll;
	// typedef pair<int,int> pii;
	typedef unsigned long long ull;
	typedef unsigned uint;
	#define Endl putchar('\n')
	// #define int long long
	// #define int unsigned
	// #define int unsigned long long
	
	#define cg (c=getchar())
	template<class T>inline void readin(T& x){
	    char c;bool f=0;
	    while(cg<'0'||'9'<c)f|=(c=='-');
	    for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
	    if(f)x=-x;
	}
	template<class T>inline T readin(T x){
	    x=0;char c;bool f=0;
	    while(cg<'0'||'9'<c)f|=(c=='-');
	    for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
	    return f?-x:x;
	}
	template<class T>void fwrit(const T x){//just short,int and long long
	    if(x<0)return (void)(putchar('-'),fwrit(-x));
	    if(x>9)fwrit(x/10);
	    putchar(x%10^48);
	}
	#undef cg
	template<class T>inline T Max(const T x,const T y){return x<y?y:x;}
	template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
	template<class T>inline T fab(const T x){return x>0?x:-x;}
	inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
	inline void getInv(int inv[],const int lim,const int MOD){
	    inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
	}
	inline ll mulMod(const ll a,const ll b,const ll mod){//long long multiplie_mod
	    return ((a*b-(ll)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
	}
}
using namespace IO;

const int maxn=3000;
const int mod=998244353;

inline int inv(int a){
	int n=mod-2,ret=1;
	for(;n>0;n>>=1,a=1ll*a*a%mod)if(n&1)ret=1ll*ret*a%mod;
	return ret;
}

int f[maxn+5][maxn*2+5];

inline void Init(){
	f[1][1]=f[1][2]=1;
	int tmp=1;
	rep(i,2,maxn){
		tmp=1ll*tmp*(i-1)%mod*2%mod;
		rep(j,1,i*2)
			f[i][j]=
				(
				0ll+/*tmp+*/f[i-1][j-1]+
				1ll*f[i-1][j]*(2*i-j)%mod+
				(j>=2?(1ll*(j-1)*f[i-1][j-2])%mod:0)
				)
				%mod;
	}
}

int x[maxn*2+5],n,N;

signed main(){
    // freopen("stars.in","r",stdin);
    // freopen("stars.out","w",stdout);
	Init();
	cin>>n;N=(n<<1)+1;
	// rep(i,1,n){
	// 	rep(j,1,N){
	// 		printf("f[%d, %d] == %d\n",i,j,f[i][j]);
	// 	}Endl;
	// }
	rep(i,1,N)cin>>x[i];
	int ans=0;
	rep(i,1,N-1)ans=(ans+1ll*f[n][i]*(x[i+1]-x[i])%mod)%mod;
	int fac=1,pow2=1;
	rep(i,1,n)fac=1ll*fac*i%mod,pow2=1ll*pow2*2%mod;
	cout<<1ll*ans*inv(fac)%mod*inv(pow2)%mod<<'\n';
	return 0;
}
posted @ 2020-10-06 21:30  Arextre  阅读(372)  评论(0编辑  收藏  举报