AtCoder Beginner Contest 270 G,Ex

y1s1,G和Ex在推等比数列式子上是相似的。

G
前置知识:BSGS(其实就是根号讨论)

首先我们展开这个递归式:

XiAiS+j=0i1AjBmodP

感觉第一项有些难搞,故我们设Fi,为:

FiAi+1S+j=0iAjBmodP

之后我们套用BSGS的方法,设答案为kx+b

则:

Fkx+b=Akx+b+1S+j=0kx+bAjB=AkxAb+1S+j=0kxAjB+Akxj=1bAjB

提取公因式:

Fkx+b=Akx(Ab+1S+j=1bAjB)+j=0kxAjB

我们先进行预处理:枚举i=0κ1,将 Ai+1S+j=1iAjBP的值存入map中,如果已经在map中有这个值了,那么我们取i最小的(因为我们要让答案最小)。

之后枚举x,查询Gj=0κxAjBAκxmodP是否在map中,如果在,更新答案ans=min(ans,κx+C),其中C是map中预处理得到的最小的值。

若我们设κ=70000,时间复杂度最优,大概是O(nlogn)

Ex
一道好题。

首先n个计数器是不好记录状态的。

一、简化并列出状态

我们设状态k=max(AiCi),其中Ci是第i个计数器的值。

可以发现,若k0,则我们就满足了CiAi,并且,其实我们k在每次操作最多减去1,故当k=0时,我们就终止操作。

这样有什么好处呢?

转移是更方便的:

我们设r,满足Ar<kAr+1,则:

  1. 如果我们将第i个计数器清零,且ir,那么k=k1。(因为Ai<k,AiCi也一定不超过k,此时k只能是由j>rAjCj转移而来)

  2. 如果我们将第i个计数器清零,且i>r,那么k=Ai。 (因为Ai>k,将第i个计数器清零后AiCi=Ai>k,故此时k=Ai

至此,我们发现k=max(AiCi),不仅减少状态数,并且转移还是简单的。

我们可以列出DP方程:F(k)表示状态为k时的期望操作次数,则F(k)=rnF(k1)+1ni=r+1nF(Ai)+1F(MAX=An)=0容易发现,这个转移是有环的。

二、处理转移有环的问题

我首先感觉这题和 https://atcoder.jp/contests/abc189/tasks/abc189_f 是类似的,设F(k)=K(k)F(MAX)+B(k)不过没继续往下想。

这题题解有一个非常巧妙的“移项去环”的方法:

我们将两侧乘上1

F(k)=rn(F(k1))+1ni=r+1n(F(Ai))1

两边同时加上F(MAX)

F(MAX)F(k)=rn(F(MAX)F(k1))+1ni=r+1n(F(MAX)F(Ai))1

P(k)=F(MAX)F(k),则:

P(k)=rnP(k1)+1ni=r+1nP(Ai)1

P(k1)移到左边,P(k)移到右边:

P(k1)=nrP(k)1ri=r+1nP(Ai)+nr

即:

P(k)=nrP(k+1)1ri=r+1nP(Ai)+nr

此时,我们发现,右侧的每个下标都大于左侧,那么这个转移是可行的。

并且最终答案是F(MAX)=F(MAX)F(0)=P(0)

时间复杂度为O(MAX)O(MAXlogMOD),TLE,怎么办?

三、优化

我们发现,对于k=aik=ai+11,时,r=i。(注意我们将下标统一+1了,现在的r其实是k+1时刻的r)那么转移的系数、常数都是相同的。

重设P(i)k=ai时的P

α=ni,β=1ij=i+1nP(Aj)+ni,θ=P(i+1),δ=ai+1ai,则类似之前的G题,将递推式展开:

P(i)=βδθ+i=0δ1βiα

前面是一个快速幂,后面是一个等比数列,可以在logMOD+logMAX的时间复杂度内求出。

由于题中保证A1=0,故答案等于P(1)

注意δ=0的情况P(i)=P(i+1)=θ

总时间复杂度为O(nlogMOD+nlogMAX)

代码:

#include<bits/stdc++.h>
#define debug(...) std::cerr<<#__VA_ARGS__<<" : "<<__VA_ARGS__<<std::endl

using ll=long long;

const int maxn=200005;
const ll mod=998244353;

ll n,sum,a[maxn],P[maxn];

ll qpow(ll x,ll y) {
	if(y==0ll) return 1ll;
	ll ret=qpow(x,y>>1ll);
	ret=ret*ret%mod;
	if(y&1ll) ret=ret*x%mod;
	return ret;
}

int main() {
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	//P[n]=0 and P[1] is the answer (P[i]=F[a[n]]-F[a[i]])
	for(int i=n-1;i>=1;i--) {
		ll alpha=n*qpow(i,mod-2ll)%mod;
		ll beta=(n-sum+mod)%mod*qpow(i,mod-2ll)%mod;
		ll theta=P[i+1];
		ll delta=a[i+1]-a[i];
		if(delta==0) P[i]=theta;
		else P[i]=(theta*qpow(alpha,delta)%mod+(1-qpow(alpha,delta)+mod)%mod*qpow((1-alpha+mod)%mod,mod-2ll)%mod*beta%mod)%mod;
		(sum+=P[i])%=mod;
	}
	printf("%lld\n",P[1]);
	return 0;
}
posted @   Nastia  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示