小 Y 和恐怖的奴隶主 题解

传送门

题解

其实这题很容易想到状压dp,设 d p i , S dp_{i,S} dpi,S为当前还有 i i i点伤害需要释放,并且敌人目前战况为 S S S时对Boss造成伤害的期望值

n n n特别大怎么办?矩阵快速幂!

根据数学推导,设 t o t tot tot为战况的总数,则 t o t ≤ 166 tot\le166 tot166,复杂度 O ( T t o t 3 log ⁡ n ) O(Ttot^3\log n) O(Ttot3logn)

等等!这样不会超时吗?

考虑做一个小优化:首先,矩阵乘法的复杂度其实是 O ( n m k ) O(nmk) O(nmk),不一定是 O ( t o t 3 ) O(tot^3) O(tot3)

而一个方阵乘一个向量是 O ( t o t 2 ) O(tot^2) O(tot2)

所以可以利用这个特性,预处理矩阵的 2 i 2^i 2i次幂,每次查询时找出需要的那些矩阵即可

类似的方法可用于矩阵快速幂的题目中查询特别多的情况,复杂度 O ( t o t 3 log ⁡ n + T t o t 2 log ⁡ n ) O(tot^3\log n+Ttot^2\log n) O(tot3logn+Ttot2logn)

这题特别卡常(我最后一个点TLE了好几次),具体实现时可以参照我的代码中的一些小优化

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll p=998244353,P=(0x7fffffffffffffffll/p-p)*p,N=170;
ll q,n,m,k,m1,x[20],y[20],first;
ll a[N];
void upd(ll &a,ll b){a+=b;if(P<=a)a-=P;}//卡常优化:开大模数
ll hx(){
	ll ans=0;
	for(ll i=1;i<=m1;i++)(ans<<=1)|=x[i];
	return ans;
}
void xh(ll h){
	for(ll i=m1;i;i--)x[i]=h&1,h>>=1;
}
ll expo(ll a,ll b){
	ll c=1;
	while(b){
		if(b&1)(c*=a)%=p;
		(a*=a)%=p;
		b>>=1;
	}
	return c;
}
ll inv(ll a){
	return expo(a,p-2);
}
void dfs(ll l,ll t){
	if(m1<l&&t==m)a[++n]=hx();
	if(m1<l)return;
	x[l]=0,dfs(l+1,t);
	if(t<m)x[l]=1,dfs(l+1,t+1);
}
ll find(ll x){
	return lower_bound(a+1,a+n+1,x)-a;
}
struct mat{
	ll a[N][N];
	mat mul(mat &b){
		mat ans;
		for(ll i=0;i<=n;i++)for(ll j=0;j<=n;j++)ans.a[i][j]=0;
		for(ll i=0;i<=n;i++)for(ll k=0;k<=n;k++){
			if(!b.a[i][k])continue;//卡常优化:由于矩阵是一个稀疏矩阵,所以可以直接跳过0
			for(ll j=0;j<=n;j++)upd(ans.a[i][j],a[k][j]*b.a[i][k]);
		}
		for(ll i=0;i<=n;i++)for(ll j=0;j<=n;j++)ans.a[i][j]%=p;
		return ans;
	}
}ma,init[70];
struct vec{
	ll a[N];
	vec mul(mat &b){
		vec ans;
		for(ll i=0;i<=n;i++){
			ans.a[i]=0;
			for(ll k=0;k<=n;k++)upd(ans.a[i],a[k]*b.a[i][k]);
		}
		for(ll i=0;i<=n;i++)ans.a[i]%=p;
		return ans;
	}
}fs,ans;
int main(){
	scanf("%lld%lld%lld",&q,&m,&k),m1=m+k;
	dfs(1,0),ma.a[0][0]=fs.a[0]=1;
	for(ll i1=1;i1<=n;i1++){
		xh(a[i1]);
		ll flag=1;
		for(ll i=2;i<=m+1;i++)if(!x[i])flag=0;
		if(flag)first=i1;
		for(ll i=1;i<=m1;i++)y[i]=x[i];
		ll tot=1;
		for(ll i=1,j=m;j;j-=y[i++])tot+=y[i]^1;
		tot=inv(tot);
		ma.a[i1][0]=ma.a[i1][i1]=tot;
		for(ll i=2;i<=m1;i++)if(y[i]&&(!y[i-1])){
			ll tot2=0;
			for(ll j=i-1;j&&!y[j];j--)tot2++;
			for(ll j=1;j<=m1;j++)x[j]=y[j];
			swap(x[i-1],x[i]);
			ll flag=0;
			for(ll j=i+1;j<=m1;j++)if(y[j])flag=1;
			if(flag&&!y[m1]){for(ll j=m1;j!=1;j--)x[j]=x[j-1];x[1]=0;}
			ma.a[i1][lower_bound(a+1,a+n+1,hx())-a]=tot*tot2%p;
		}
	}
	init[0]=ma;
	for(ll i=1;i<61;i++)init[i]=init[i-1].mul(init[i-1]);
	while(q--){
		ans=fs;
		ll a,i=0;scanf("%lld",&a);
		while(a){
			if(a&1)ans=ans.mul(init[i]);++i;
			a>>=1;
		}
		printf("%lld\n",ans.a[first]);
	}
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值