群星陨落时

280 篇文章 1 订阅
220 篇文章 2 订阅

题目

传送门 to usOJ

原题名叫 “客星璀璨之夜” ,我结合了一下自己的实际情况,改成了 “群星陨落时” 。

思路

来看看大家怎样爆切这道题的吧!顺便看看我是怎么惨痛 20 20 20 分的。

沐目女未

看不懂 😢 丢个链接就算了吧。

木示木干

真正的期望 d p \tt dp dp 学懂了的男人就是不一样。重要思想是 每一步很笼统的,只考虑当前这一步的概率

不妨看看,然后模。 x y x ,    x j b g \tt xyx,\;xjbg xyx,xjbg

土立土及

当然是我这个垃圾 😢

我感觉蛮有道理的——就是先计算每种情况下的移动距离和,然后除以总情况数。

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 6010;
const int Mod = 998244353;
int dp[MaxN][MaxN]; // 移动距离求和
int g[MaxN][MaxN]; // 必须是大括号
int f[MaxN]; // 方案数
int x[MaxN], c[MaxN][MaxN], inv[MaxN];

int main(){
	int n = readint();
	c[0][0] = 1; // maybe useful
	for(int i=1; i<=2*n+1; ++i){
		x[i] = readint();
		for(int j=c[i][0]=1; j<=i; ++j)
			c[i][j] = (c[i-1][j-1]
				+c[i-1][j])%Mod;
	}
	f[1] = 1;
	for(int i=3; i<=2*n+1; i+=2)
		f[i] = 1ll*i*f[i-2]%Mod;
	for(int i=1; i<=2*n; ++i){
		dp[i][i+1] = x[i+1]-x[i];
		g[i][i+1] = x[i+1]-x[i];
	}
	for(int l=3; l<=(n<<1); l+=2)
	for(int i=1; i+l<=2*n+1; ++i){
		for(int j=i+1; j<i+l; j+=2){
			/**
			 * 左边 [i,j] 要操作 (j-i+1)/2 次
			 * 共操作 (l+1)/2 次,选出一些为左边
			 * 所以方案数是 C_{(l+1)/2}^{(j-i+1)/2}
			 **/
			int_ k = c[(l+1)>>1][(j-i+1)>>1];
			dp[i][i+l] += (g[i][j]*f[i+l-j-1]%Mod
				+ dp[j+1][i+l]*f[j-i]%Mod)*k%Mod;
			dp[i][i+l] %= Mod;
		}
		g[i][i+l] = 1ll*f[l-2]*(x[i+l]-x[i])%Mod;
		g[i][i+l] = (g[i][i+l]+dp[i+1][i+l-1])%Mod;
		dp[i][i+l] = (dp[i][i+l]+g[i][i+l])%Mod;
	}
	
	int ans = (dp[1][2*n]+dp[2][2*n+1])%Mod;
	for(int i=3; i<(n<<1); i+=2){
		int_ k = c[n][i>>1];
		ans += k*(dp[1][i-1]
			+ dp[i+1][2*n+1])%Mod;
		ans %= Mod;
	}

	inv[1] = 1;
	for(int i=2; i<=n; ++i){
		inv[i] = (0ll+Mod-Mod/i)*inv[Mod%i]%Mod;
		ans = 1ll*ans*inv[i]%Mod; // ÷ (n!)
		ans = 1ll*ans*inv[2]%Mod; // ÷ (2^n)
	}
	ans = 1ll*ans*inv[2]%Mod;
	printf("%d\n",ans);
	return 0;
}

/*

假如 i 撞到了 j
那么 i,j 之间的一定全部抵消
有点像括号匹配??
是了!合法括号匹配与其唯一对应!
并列括号 ()() 顺序任意
嵌套括号 (()) 内先于外
-------------------------
每一步有 (行星数量)*2 种选择
所以总方案数是 (n!)*(2^n) 可以最后除掉
-------------------------
f(i,j) 表示 [i,j] 成为完整的括号序列

*/

然后只有暴力的二十分。事实上打暴力的过了三十。

一种正解

计算每一小段期望被覆盖多少次。用 f ( n , i ) f(n,i) f(n,i) 表示,第 i i i 个间隙(也就是 x i + 1 − x i x_{i+1}-x_i xi+1xi 对应的一段)被覆盖的期望次数。当然,这个下标是按照共 2 n + 1 2n+1 2n+1 个天体来计算的。

此时可以枚举第一次操作。如果此行星和恒星均在 x i x_i xi 左边(含 x i x_i xi 本身),那么转移到 f ( n − 1 , i − 2 ) f(n-1,i-2) f(n1,i2) 。类似地,如果都在 x i + 1 x_{i+1} xi+1 右边(含 x i + 1 x_{i+1} xi+1 本身),那么转移到 f ( n − 1 , i ) f(n-1,i) f(n1,i)

如果此恒星和行星恰好为 x i x_i xi x i + 1 x_{i+1} xi+1 ,对应的期望次数是 f ( n − 1 , i − 1 ) + 1 f(n-1,i-1)+1 f(n1,i1)+1

这三类分别乘上概率再累加即可。任意两个相邻的都可能互相湮灭,很好算概率。复杂度 O ( n 2 ) \mathcal O(n^2) O(n2)

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x%10)^48);
}
inline int qkpow(int_ b,int q,int m){
	int ans = 1;
	for(; q; q>>=1,b=b*b%m)
		if(q&1) ans = ans*b%m;
	return ans;
}

const int Mod = 998244353;
const int MaxN = 6010;
int inv[MaxN], f[MaxN>>1][MaxN];

int main(){
	inv[1] = 1;
	for(int i=2; i<MaxN; ++i)
		inv[i] = (0ll+Mod-Mod/i)
			*inv[Mod%i]%Mod;
	for(int i=1; i<(MaxN>>1); ++i)
	for(int j=1; j<=(i<<1); ++j){
		f[i][j] = ((f[i-1][j-1]+1)
			+ (j-1ll)*f[i-1][j-2]
			+ (2ll*i-j)*f[i-1][j])%Mod;
		f[i][j] = 1ll*f[i][j]*inv[i<<1]%Mod;
	}

	int n = readint(), ans = 0;
	int_ lst = readint();
	for(int i=1,x; i<=(n<<1); ++i,lst=x){
		x = readint();
		ans = (ans+(x-lst)*f[n][i])%Mod;
	}
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值