BZOJ5381 HNOI2018省队集训 OR

博客围绕BZOJ上HN省队集训题展开,原题为Codeforces623E Transforming Sequence。解题时发现n>k答案为0,设f[n][i]表示方案数,原转移方式慢,考虑合并多个数,采用DP快速幂和NTT优化卷积,最终答案为∑if[n][i](ik),时间复杂度O(klog2k)。

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

Problem

BZOJ
竟然是HN省队集训的题,出题人还贴心地把模数改成了 998244353 998244353 998244353,好感动,我还以为HN省队集训都是一堆毒瘤出自己都不会/想做的题。
原题应该是这个,只不过原题要MTT:Codeforces623E Transforming Sequence

Solution

首先 n n n 的范围是假的,因为要严格递增,每次 b i b_i bi 至少会有1位由0变成1,因此 n > k n>k n>k 时答案为0。

f [ n ] [ i ] f[n][i] f[n][i] 表示前 n n n 个数的或和用了 i i i 位的方案数,那么
f [ n ] [ i ] = ∑ j = 1 i f [ n − 1 ] [ i − j ] ( i j ) 2 i − j f[n][i]=\sum_{j=1}^i f[n-1][i-j]\binom {i} {j}2^{i-j} f[n][i]=j=1if[n1][ij](ji)2ij
但这样还是太慢了,注意到转移关系和第一维其实没有关系,那么不妨考虑能不能一次合并多个数
f [ x + y ] [ i ] = ∑ j = 1 i f [ x ] [ j ] ( i j ) f [ y ] [ i − j ] 2 j y = i ! ∑ j = 0 i f [ x ] [ j ] 2 j y j ! ⋅ f [ y ] [ i − j ] ( i − j ) ! f[x+y][i]=\sum_{j=1}^i f[x][j]\binom i j f[y][i-j] 2^{jy}=i!\sum_{j=0}^i \frac {f[x][j]2^{jy}} {j!}\cdot\frac {f[y][i-j]} {(i-j)!} f[x+y][i]=j=1if[x][j](ji)f[y][ij]2jy=i!j=0ij!f[x][j]2jy(ij)!f[y][ij]

DP快速幂+NTT优化卷积即可,最后的答案为 ∑ i f [ n ] [ i ] ( k i ) \sum_{i} f[n][i] \binom k i if[n][i](ik)

时间复杂度 O ( k log ⁡ 2 k ) O(k\log^2 k) O(klog2k)

Code

#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=150010,mod=998244353,G=3;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
int k,N,l,ans,rev[maxn],mi[maxn],fac[maxn],inv[maxn],res[maxn],tmp[maxn];
ll n;
int pls(int x,int y){return x+y>=mod?x+y-mod:x+y;}
int dec(int x,int y){return x<y?x-y+mod:x-y;}
int C(int n,int m){return m>n?0:(ll)fac[n]*inv[m]%mod*inv[n-m]%mod;}
int power(int x,ll y)
{
	int res=1;
	for(;y;y>>=1,x=(ll)x*x%mod)
	  if(y&1)
	    res=(ll)res*x%mod;
	return res;
}
void NTT(int *a,int f)
{
	for(int i=1;i<N;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
	for(int i=1;i<N;i<<=1)
	{
		int gn=power(G,(mod-1)/(i<<1));
		for(int j=0;j<N;j+=(i<<1))
		{
			int g=1,x,y;
			for(int k=0;k<i;++k,g=(ll)g*gn%mod)
			{
				x=a[j+k];y=(ll)a[j+k+i]*g%mod;
				a[j+k]=pls(x,y);a[j+k+i]=dec(x,y);
			}
		}
	}
	if(f==-1)
	{
		int iv=power(N,mod-2);reverse(a+1,a+N);
		for(int i=0;i<N;i++) a[i]=(ll)a[i]*iv%mod;
	}
}
void mul(int x,int y,int *fx,int *fy,int *c)
{
	static int a[maxn],b[maxn];
	for(int i=0;i<=k;i++) a[i]=(ll)fx[i]*power(2,(ll)i*y)%mod*inv[i]%mod;
	for(int i=0;i<=k;i++) b[i]=(ll)fy[i]*inv[i]%mod;
	for(int i=k+1;i<N;i++) a[i]=b[i]=0;
	NTT(a,1);NTT(b,1);
	for(int i=0;i<N;i++) a[i]=(ll)a[i]*b[i]%mod;
	NTT(a,-1);
	for(int i=0;i<=k;i++) c[i]=(ll)a[i]*fac[i]%mod;
}
int main()
{
	read(n);read(k);fac[0]=mi[0]=1;
	if(n>k){puts("0");return 0;}
	for(int i=1;i<=k;i++) fac[i]=(ll)fac[i-1]*i%mod,mi[i]=pls(mi[i-1],mi[i-1]);
	inv[k]=power(fac[k],mod-2);
	for(int i=k-1;~i;i--) inv[i]=(ll)inv[i+1]*(i+1)%mod;
	for(N=1,l=0;N<=(k<<1);N<<=1) ++l;
	for(int i=1;i<N;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
	for(int i=1;i<=k;i++) res[i]=tmp[i]=1;
	--n;
	for(int i=1,j=1;i<=n;i<<=1,j+=i&n)
	{
		if(n&i) mul(j,i,res,tmp,res);
		mul(i,i,tmp,tmp,tmp);
	}
	for(int i=0;i<=k;i++) ans=(ans+(ll)res[i]*C(k,i))%mod;
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值