Atcoder题解:Agc056_e

先把当前要解决的点旋转到位置 n,问题不变。求 n 次即可。

我们先来看两个没有结果的解法。

一就是一

我们先考虑暴力 dp,设 dpi,mask 表示当前已经安放了 i 个奶酪,被喂饱的老鼠的集合为 mask 的概率。容易发现 i 其实就是 mask1 的个数。

然后考虑暴力枚举下一步用哪里的奶酪喂哪里的老鼠,我们发现,在还剩 i 个老鼠的时候,不套整圈,用 x 的奶酪贡献到 y ,一共会经过 dist(mask,x,y) 次老鼠。而 dist(mask,x,y) 表示 mask 中的老鼠已经被喂饱了的情况下,在没喂饱的老鼠中,x 处的奶酪贡献到 y 处的老鼠,奶酪会经过没喂饱的老鼠几次。

然后考虑套整圈的情况,其实就是先走到老鼠 b,然后枚举绕多少圈,设还剩下 d 个老鼠,贡献就是 n=012nd=112d,算起来,贡献就是 112dx,y(ax%)2dist(mask,x,y)

复杂度 O(n22n)

我们发现在任何从 dp0dp2n1的转移路线中,一定会经过 d2n 的所有情况,也就是,d=2n112d 是每个最终状态都有的系数。可以从转移里提出这一部分,然后在最终答案里乘上就可以了。

我们发现 ax% 也是可以优化的,首先 1100n1 可以直接提出来。然后我们发现 ax 是整数,所以我们可以改变问题为全环上一共有 100 个奶酪投放点,第 i 个位置前面有 ai 个,每次在其中等概率随机一个。

x 改成枚举的奶酪投放的地点,我们就可以优化掉 ax%

这样,我们的问题就简单的变成了,(π,x)i=1n12dist(i1,x(i),π(i)),实际上就是对所有的二元组 (π,x) 算贡献。而 π 是其他老鼠被吃掉的顺序的排列,x 是一个向量,取值 (1,100)

这样我们就可以得到一个 O(1002n)dp,因为确定 x(i) 之后,可以递推的确定 dist。然而,我们发现,这个方法到这里就结束了。

为什么呢?因为 dist 是和前面的选择直接相关的,因此保持这个思路我们的 2n 是不太可能优化下去的。

或许我们抱有一丝期冀,因为我们发现,我们可以把 dist 转化成贡献,也就是 x(i)π(i) 的贡献、π(j)(x(i),π(i)) 的贡献(j<i)。然后我们就可以拆分每个 dist 的贡献,枚举进行贡献的 π(j),x(i)π(i) 进行组合得到答案。

但是这个梦想也是不现实的,因为首先,2 的指数加和再求和的方式无法通过平均期望的方式算平均贡献,(当前的 12 贡献在哪些状态上是具有后效性的),所以重设 dp 状态为 xxx 的和或者 xxx 的期望来计算这个东西就不可能了。

然后,对这个贡献进行 dp 也是行不通的,因为各个贡献不是独立的,必须满足一个偏序关系,也就是如果 π(j)π(i) 贡献,π(k)π(j) 贡献,π(i) 就不能向 π(k) 贡献,所以我们又需要记录哪些位置已经用过了,回到 2n 的复杂度。

二就是二

然后考虑另一种做法,破环为链,我们将围绕这个关键词进行一系列尝试。

首先我们一定是在 1n 之间把环破开。然后我们考虑这样一件事情:

我们把所有的到达 n 还没有答案的奶酪都删除掉,映射到在 1 位置放上一个奶酪。这是什么意思呢?我们钦定所有的绕很多圈的奶酪都仅计算从 1 到目的地的最后这一段。

这样,我们可以重新计算各个 ai 表示从 i 出发一个奶酪,喂食一个 [i,n1] 中的老鼠的概率。

破环成链成功,如果 ai 是个定值,我们就可以 dp 了。我们只要记录当前的奶酪个数,就可以 dp

可惜的是,ai 不是定值,甚至于,ai 依旧和前面选择老鼠的详细方案有关。而只要牵扯到详细方案就免不了状压,也就走向了彻底的失败。

为什么 ai 和前面选择老鼠的详细方案有关呢?因为它是和当前点后面有多少个点可以选择有关的,具体而言 ai=ai2k12k,而 a1 就是 1i1ai。而我们要对于每个位置知道其后面的点的个数,就等价于要知道当前剩下的点的集合。

我们经历了又一次失败,但是希望的火种已经在这里诞生了。

没有三了

然后,让我们分析前两次尝试失败的原因。

我们发现,我们的问题缺少一个转化,在没有问题转化的条件下,其最终会演变成第一个做法所提及的排列和向量的二元组贡献,然后这个问题的答案就必将和详细的选择方案有关,然后就必须保留这个信息(存在后效性)。

以此,我们来尝试第三个做法。我们尝试一开始就把所有的奶酪放下来。

我们先破环成链,然后复制两遍,从第一个开始,设 fi,j 表示当前来到第 i 个老鼠,还剩 j 块奶酪的概率。

然后,我们可以做的第一件事是在这个位置放新的奶酪,枚举个数,注意 i+j1>n1 的状态是不合法的,因为我们最多安放 n1 个奶酪。

接下来,我们就需要贡献了。我们发现,一个奶酪经过一个老鼠可以有三种可能:

  • 以 0.5 的概率经过老鼠,要求这个奶酪出现在老鼠被喂饱前

  • 以 0.5 的概率被老鼠吃掉

  • 直接通过老鼠,要求这个奶酪出现在老鼠被喂饱后

然后我们可以枚举直接通过的奶酪个数……

发现这里又出现新的问题了。奶酪是否直接通过,也是和奶酪的位置有关的。换句话说,我们虽然表面上解决了老鼠的顺序,却没能解决奶酪的顺序。然后奶酪和老鼠配对,等于我们还是没有解决老鼠的顺序。

但是这里已经给予了我们通向最终正解的道路。我们尝试将这个算法和第二个结合起来……

五在这里

我们来思考二和三的特点。

二的特点是破环成链之后破整为零,只去研究每一块奶酪最后一次经过 n 之后的结果。弊端是没能省去老鼠的顺序。

三的特点是一开始就不去区分所有的奶酪,按照位置而非时间顺序去放置奶酪,最终统一计算。弊端是没能奶酪的顺序还是对算法存在影响,但是已经把老鼠的顺序转化成了别的东西。同时,我们也没有考虑绕整圈的问题。

Cheese访





Z

我们发现,我们可以先破环成链,然后放下所有的奶酪,然后处理出“它们到 n 的概率。如果在没到 n 之前就喂老鼠了,就喂老鼠了吧。

也就是说,我们先处理它们从 xy 或者 n 的链贡献。

接下来是本题的精华所在:

在一个节点的位置,不同时间的奶酪经过,谁留下来、谁付出、谁掠过,都是一样的。

为什么呢?

因为,我们在这一节点可以随便安排所有奶酪的顺序,比如说,本来 AB 前面,所以理应是 A 经过,B 被吃掉。但是我们完全可以让 A 被吃掉,B 经过,后面出现的所有 A 都变成 B,不会对其他的点的略过造成影响,这是等价的。

所以奶酪的顺序反而无关紧要了。而且,因为如果 A 略过,B 被吃掉,B 换成 A,实际上这个方案是不合法的,我们只是把它当作一个原先的问题的替代就可以了。而原先问题就可以划分成一干等价类,而这其中只有一个是成立的,也就是最终被我们安排在最后一个的吃掉它。我们也只对等价类计算一次贡献即可。

这是至关重要的!

因为我们通过“等价代换两种不同出现时间的方案”,消除了奶酪出现时间对答案的影响!

这就太开心了!现在我们的奶酪和老鼠都变成了按照下标为顺序的,我们只要把老鼠和奶酪配对好,然后随意安排奶酪的顺序。需要注意的是,因为奶酪可能出现在同一个位置,所以还需要在转移的时候做一个多重集。

dpi,j,k 表示当前 dp 到第 i 个位置,放置了 j 块奶酪,一共喂过了 k 只老鼠。

不过,i 的维度每次会有两次转移,还是滚动掉好(第一次见到为了正确性不得不滚动的)。

每次先枚举放 l 块奶酪(注意 j+k+l 不能超出限制,在放置的时候就先把多重集的 1l! 乘进去,同时还有 ail,可以预处理一手以保证复杂度。

然后看是喂还是不喂。不喂的话,所有的奶酪都应当经过它,贡献就是 2j。喂的话,对于所有 2j 种贡献和不贡献的选择,每种所代表的等价类里面恰好有一个是合法的,所以直接进行一次贡献。除了谁都不贡献,这是不可以的,所以要减去一种。对答案的贡献是 2j12j

然后我们得到了到达 n 的结果。接下来呢?还剩下 nk1 个老鼠和奶酪。

我们先让它们都贡献一次 n,从 1 重新开始。

然后我们发现,我们剩下的奶酪怎么选是独立的了!首先我们用第一种做法中的结论把 d=2nk112d 提出来,然后接下来,剩下的奶酪中的第 i 个就必须在 [1,i1] 中选一个,也就是除了最后一个都能选,贡献是 i=1nk1(12i)

然后把奶酪的多重集全排列的 (n1)! 乘上,我们惊喜的发现,这个做法的复杂度是 O(n5),我们解决了这道题!

const ll P=998244353;
inline ll fpow(ll a,ll p){
	if(!p)return 1ll;
	ll res=fpow(a,p>>1);
	if(p&1)return res*res%P*a%P;
	return res*res%P;
}
inline ll inv(ll a){
	return fpow(a,P-2);
}
ll ff=1,n,a[45],x[45],C[45][45],fac[45],ifac[45],pw[45],ipw[45];
ll dp[45][45],pwa[45][45];
inline void init(){
	rep(i,0,40){
		C[i][0]=1;
		rep(j,1,i)C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
	}fac[0]=1,ifac[0]=1,pw[0]=1,ipw[0]=1;
	rp(i,40)fac[i]=fac[i-1]*i%P;
	rp(i,40)ifac[i]=inv(fac[i]);
	rp(i,40)pw[i]=pw[i-1]*2%P;
	rp(i,40)ipw[i]=inv(pw[i]);
	rd(i,n)a[i]=a[i]*inv(100)%P;
}
inline void rotate(int d){
	rd(i,n)x[(i-d-1+n)%n]=a[i];
	rd(i,n){
		pwa[i][0]=1;
		rp(j,n)pwa[i][j]=pwa[i][j-1]*x[i]%P;
	}
}
inline void solve(int cur){
	rotate(cur);
	rep(i,0,n)rep(j,0,n)rep(k,0,n)dp[j][k]=0;
	dp[0][0]=1;
	rep(i,0,n-2){
		per(j,0,n-1){
			rep(k,0,n-1){
				rep(l,1,n-j-k){
					dp[j+l][k]=(dp[j+l][k]+dp[j][k]*pwa[i][l]%P*ifac[l]%P)%P;
				}
			}
		}
		rep(j,0,n-1){
			per(k,0,n-1){
				if(dp[j][k]){
					if(k!=n-1){
						if(j)dp[j-1][k+1]=(dp[j-1][k+1]+dp[j][k]*(1-ipw[j]+P)%P)%P;
						dp[j][k]=dp[j][k]*ipw[j]%P;
					}
				}
			}
		}
	}
	per(j,0,n-1){
		rep(k,0,n-1){
			rep(l,1,n-j-k){
				dp[j+l][k]=(dp[j+l][k]+dp[j][k]*pwa[n-1][l]%P*ifac[l]%P)%P;
			}
		}
	}
	ll ans=0;
	rep(k,0,n-1){
		if(dp[n-k-1][k]){
			ll res=1;
			rep(x,1,n-1-k){
				res=(1-ipw[x]+P)%P*inv((1-ipw[x+1]+P))%P*res%P;
			}
			ans=(ans+dp[n-k-1][k]*ipw[n-1-k]%P*fac[n-1]%P*res%P)%P;
		}
	}cout<<ans<<" ";
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	rd(i,n)cin>>a[i];
	init();
	rd(i,n)solve(i);
	return 0;
}
//Crayan_r
posted @   jucason_xu  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示