【期望DP+NTT优化】LOJ565 [LibreOJ Round #10] mathematican 的二进制

本文解析了一道涉及概率计算的算法题,通过分析独立操作的贡献,将问题分解为加1操作和进位操作两部分。利用多项式表示和NTT技术,在O(mlog^2m)的时间复杂度内解决该问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目】
原题地址
给定一个长度为 n n n的二进制,初始每一位都为 0 0 0,现在有 m m m个操作,第 i i i个操作是给它加上 2 a i 2^{a_i} 2ai,操作的代价是这次操作改变的位的数量。对于第 i i i个操作有 p i p_i pi的概率执行,问操作代价和的期望。

【解题思路】
一道并不是很难的概率题,因为一个小地方符号写错想了半天。
首先分析题目,每个操作是独立的,因此我们可以分开考虑,那么就会分成两部分:对于某位加 1 1 1,进位。
首先考虑对于某位加 1 1 1的操作,我们设 f i , j f_{i,j} fi,j表示第 i i i位仅通过在这位上的操作贡献 j j j次的概率,那么第 i i i位新加进来一个操作,我们有: f i , j ′ = ( 1 − p ) × f i , j + p × f i , j − 1 f'_{i,j}=(1-p)\times f_{i,j}+p\times f_{i,j-1} fi,j=(1p)×fi,j+p×fi,j1,那么我们将第二维用一个多项式来表示,则相当于乘上 ( ( 1 − p ) + p x ) ((1-p)+px) ((1p)+px)这个多项式。特别地,一开始 f i , 0 = 1 f_{i,0}=1 fi,0=1。于是加操作我们可以用堆启发式合并 + NTT +\text{NTT} +NTT O ( m l o g 2 m ) O(mlog^2m) O(mlog2m)的时间内处理完。

现在考虑进位,我们设 g i , j g_{i,j} gi,j表示第 i i i位一共 j j j次的概率,那么我们不难得到: g i , j = ∑ ⌊ a 2 ⌋ + b = j g i − 1 , a × f i , b g_{i,j}=\sum_{\lfloor \frac a 2 \rfloor + b=j} g_{i-1,a}\times f_{i,b} gi,j=2a+b=jgi1,a×fi,b
于是这个也是一个卷积的形式,我们分开奇数位和偶数位考虑,仍然用 NTT \text{NTT} NTT就可以做到 O ( m l o g m ) O(mlogm) O(mlogm),这个复杂度是因为实际上每次操作影响到的位数最多就是 l o g log log级别的(这个相当于暴力233)。

所以总的复杂度是 O ( m l o g 2 m ) O(mlog^2m) O(mlog2m)的。
有点卡常其实。LOJ评测语言选c++(NOI)过不了。

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define vi vector<int>
using namespace std;

typedef long long ll;
typedef pair<int,int> pii;
const int N=2e5+10,M=N<<2;
const int mod=998244353,G=3;
int n,Q,ans;

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

void up(int &x,int y){x+=y;if(x>=mod)x-=mod;if(x<0)x+=mod;}
int upm(int x){return x>=mod?x-mod:x;}
int qpow(int x,int y)
{
	int ret=1;
	for(;y;y>>=1,x=(ll)x*x%mod) if(y&1) ret=(ll)ret*x%mod;
	return ret;
}

namespace NTT
{
	int rem,m,L,tot,rev[M],cnt[M];
	vi f[N],h,g;
	priority_queue<pii>q[N];

	void ntt(vi &a,int n,int op)
	{
		for(int i=0;i<n;++i) if(i<rev[i]) swap(a[i],a[rev[i]]);
		for(int i=1;i<n;i<<=1)
		{
			int wn=qpow(G,(mod-1)/(i<<1));
			if(op==-1) wn=qpow(wn,mod-2);
			for(int j=0;j<n;j+=(i<<1))
			{
				int w=1;
				for(int k=0;k<i;++k,w=(ll)w*wn%mod)
				{
					int x=a[j+k],y=(ll)w*a[i+j+k]%mod;
					a[j+k]=upm(x+y);a[i+j+k]=upm(x-y+mod);
				}
			}
		}
		if(op==-1) for(int i=0,inv=qpow(n,mod-2);i<n;++i) a[i]=(ll)a[i]*inv%mod;
	}
	void reget(int sz)
	{
		for(m=1,L=0;m<=sz;m<<=1) ++L; if(rem==m) return; rem=m;
		for(int i=0;i<m;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
	}
	void merge(int idx,int idy)
	{
		int sz=f[idx].size()+f[idy].size()-1;
		reget(sz);
		f[idx].resize(m);f[idy].resize(m);
		ntt(f[idx],m,1);ntt(f[idy],m,1);
		for(int i=0;i<m;++i) f[idx][i]=(ll)f[idx][i]*f[idy][i]%mod; 
		ntt(f[idx],m,-1);f[idx].resize(sz);
		//for(int i=0;i<sz;++i) f[idx][i]=C[i]; 
	}
	void calc(int k)
	{
		if(!q[k].empty())
		{
			int a=q[k].top().se;
			for(int i=0;i<=cnt[k];++i) h[i]=f[a][i];
		}
		else h[0]=1;
		//printf("%d||",k);
		//for(int i=0;i<=cnt[k];++i) printf("%d ",h[i]); puts("");
		reget(tot);
		for(int i=cnt[k]+1;i<m;++i) h[i]=0;
		//for(int i=0;i<m;++i) printf("%d ",g[i]); puts("");
		//for(int i=0;i<m;++i) printf("%d ",h[i]); puts("");
		ntt(g,m,1);ntt(h,m,1);
		for(int i=0;i<m;++i) g[i]=(ll)g[i]*h[i]%mod;
		ntt(g,m,-1);
		for(int i=1;i<=tot;i+=2) up(ans,mod-g[i]);
		for(int i=1;i<=tot;++i) up(g[i>>1],g[i]),g[i]=0;
		//for(int i=0;i<=tot;++i) printf("%d ",g[i]); puts("");
		//printf("%d\n",ans);
	}
	void solve()
	{
		n+=30;tot=0;
		g.resize(N<<1);h.resize(N<<1);g[0]=1;
		for(int k=0;k<n;++k)
		{
			tot=tot/2+cnt[k];
			if(!tot) continue;
			while(q[k].size()>1)
			{
				int a=q[k].top().se;q[k].pop();
				int b=q[k].top().se;q[k].pop();
				merge(a,b);q[k].push(mkp(-f[a].size(),a));
			}
			calc(k);
		}
		printf("%d\n",ans);
		cerr<<clock()<<endl;
	}
	void init()
	{
		n=read();Q=read();
		for(int i=1;i<=Q;++i)
		{
			int a=read(),p=read();p=(ll)p*qpow(read(),mod-2)%mod;
			++cnt[a];up(ans,upm(p+p));
			f[i].resize(2);f[i][0]=upm(1-p+mod);f[i][1]=p;q[a].push(mkp(-2,i));
		}
	}
};

int main()
{
#ifndef ONLINE_JUDGE
	freopen("LOJ565.in","r",stdin);
	freopen("LOJ565.out","w",stdout);
#endif
	NTT::init();NTT::solve();

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值