codeforces 623E Transforming Sequence 题解

每个数其实可以看成一个由二进制位组成的集合,那么第 i i i 个数表示的集合内肯定有一个二进制位是前 i − 1 i-1 i1 个数的集合内所没有的。

于是有个结论,当 n > k n>k n>k 时,答案为 0 0 0

f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 个数字用 j j j 个二进制位的方案数,那么有:
f [ i ] [ j ] = ∑ l = 0 j − 1 2 l ( j l ) f [ i − 1 ] [ l ] f [ i ] [ j ] j ! = ∑ i = 0 j − 1 f [ i − 1 ] [ l ] 2 l l ! × 1 ( j − l ) ! \begin{aligned} f[i][j]&=\sum_{l=0}^{j-1} 2^l {j\choose l}f[i-1][l]\\ \frac {f[i][j]} {j!}&=\sum_{i=0}^{j-1} \frac {f[i-1][l]2^{l}} {l!} \times \frac 1 {(j-l)!} \end{aligned} f[i][j]j!f[i][j]=l=0j12l(lj)f[i1][l]=i=0j1l!f[i1][l]2l×(jl)!1

把中间的组合数拆开后,发现是个卷积的形式,用 F F T FFT FFT 加速一下后,时间复杂度为 O ( n k log ⁡ k ) O(nk\log k) O(nklogk)

拓展一下,这个方程还可以写成这样的形式:
f [ x + y ] [ i ] = ∑ j = 0 i 2 y × j ( i j ) f [ x ] [ j ] × f [ y ] [ i − j ] f [ x + y ] [ i ] i ! = ∑ j = 0 i f [ x ] [ j ] × 2 y × j j ! × f [ y ] [ i − j ] ( i − j ) ! \begin{aligned} f[x+y][i]&=\sum_{j=0}^i 2^{y\times j} {i\choose j} f[x][j]\times f[y][i-j]\\ \frac {f[x+y][i]} {i!}&=\sum_{j=0}^i \frac {f[x][j]\times 2^{y\times j}} {j!} \times \frac {f[y][i-j]} {(i-j)!} \end{aligned} f[x+y][i]i!f[x+y][i]=j=0i2y×j(ji)f[x][j]×f[y][ij]=j=0ij!f[x][j]×2y×j×(ij)!f[y][ij]

有了这个东西之后, f [ 2 n ] f[2n] f[2n] 可以直接从 f [ n ] f[n] f[n] 转移过来,于是倍增一下就能求出 f f f 了。

最后答案就是 ∑ i = 0 k C k i f [ n ] [ i ] \sum_{i=0}^k C_k^i f[n][i] i=0kCkif[n][i]

代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 200010
#define mod 1000000007
#define ld long double
#define ll long long
#define bin(x) (1<<(x))

ll n;int k,F[maxn],G[maxn];
int inv[maxn],fac[maxn],inv_fac[maxn];
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
void work()
{
	inv[1]=1;for(int i=2;i<=maxn-10;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	fac[0]=inv_fac[0]=1;for(int i=1;i<=maxn-10;i++)fac[i]=1ll*fac[i-1]*i%mod;
	inv_fac[maxn-10]=ksm(fac[maxn-10],mod-2);for(int i=maxn-11;i>=1;i--)inv_fac[i]=1ll*inv_fac[i+1]*(i+1)%mod;
}
int C(int x,int y){return 1ll*fac[x]*inv_fac[y]%mod*inv_fac[x-y]%mod;}
struct comp{
	ld x,y;comp(ld xx=0,ld yy=0):x(xx),y(yy){}
	comp operator +(const comp b){return comp(x+b.x,y+b.y);}
	comp operator -(const comp b){return comp(x-b.x,y-b.y);}
	comp operator *(const comp b){return comp(x*b.x-y*b.y,x*b.y+y*b.x);}
}a[maxn],b[maxn],dfta[maxn],dftb[maxn],dftc[maxn],dftd[maxn];
comp conj(comp x){return comp(x.x,-x.y);}
const ld Pi=acos(-1);
comp *w[30];void prep(int N,int lg_N){
	for(int i=1;i<=lg_N;i++){
		w[i]=new comp[bin(i-1)];w[i][0]=comp(1,0);comp wn(cos(Pi/bin(i-1)),sin(Pi/bin(i-1)));
		for(int j=1;j<bin(i-1);j++)w[i][j]=w[i][j-1]*wn;
	}
}
int limit=1,lg=0,r[maxn];void fft(comp *f)
{
	for(int i=1;i<limit;i++)if(i<r[i])swap(f[i],f[r[i]]);
	for(int mid=1,Lg=1;mid<limit;mid<<=1,Lg++)for(int j=0;j<limit;j+=(mid<<1))for(int i=0;i<mid;i++)
	{comp t=f[j+i+mid]*w[Lg][i];f[j+i+mid]=f[j+i]-t;f[j+i]=f[j+i]+t;}
}
void FFT(int *f,int *g)
{
	//注意,多次使用拆系数fft一定要加这个用来初始化的判断,我就是因为这个调了半天……
	for(int i=0;i<limit;i++)if(i>k)a[i]=b[i]=comp(0,0);
	else a[i]=comp(f[i]&32767,f[i]>>15),b[i]=comp(g[i]&32767,g[i]>>15);
	fft(a);fft(b);for(int i=0;i<limit;i++)
	{
		static comp da,db,dc,dd;
		int j=(limit-i)&(limit-1);
		da=(a[i]+conj(a[j]))*comp(0.5,0);
		db=(a[i]-conj(a[j]))*comp(0,-0.5);
		dc=(b[i]+conj(b[j]))*comp(0.5,0);
		dd=(b[i]-conj(b[j]))*comp(0,-0.5);
		dfta[j]=da*dc;dftb[j]=da*dd;
		dftc[j]=db*dc;dftd[j]=db*dd;
	}
	for(int i=0;i<limit;i++)a[i]=dfta[i]+dftb[i]*comp(0,1);
	for(int i=0;i<limit;i++)b[i]=dftc[i]+dftd[i]*comp(0,1);
	fft(a);fft(b);for(int i=0;i<2*k+1;i++)
	{
		int da=(ll)(a[i].x/limit+0.5)%mod;
		int db=(ll)(a[i].y/limit+0.5)%mod;
		int dc=(ll)(b[i].x/limit+0.5)%mod;
		int dd=(ll)(b[i].y/limit+0.5)%mod;
		f[i]=(da+((ll)(db+dc)<<15)%mod+((ll)dd<<30)%mod)%mod;
	}
}
void solve(int ln)
{
	if(ln==1){F[0]=0;for(int i=1;i<=k;i++)F[i]=1;return;}solve(ln>>1);
	for(int i=1;i<=k;i++)F[i]=1ll*F[i]*inv_fac[i]%mod,G[i]=1ll*F[i]*ksm(2,1ll*(ln>>1)*i%(mod-1))%mod;
	FFT(F,G);if(ln&1){for(int i=1;i<=k;i++)F[i]=1ll*F[i]*ksm(2,i)%mod,G[i]=inv_fac[i];FFT(F,G);}
	for(int i=1;i<=k;i++)F[i]=1ll*F[i]*fac[i]%mod;
}

int main()
{
	scanf("%lld %d",&n,&k);work();
	if(n>k)return printf("0"),0;
	while(limit<=2*k+2)limit<<=1,lg++;prep(limit,lg);
	for(int i=1;i<limit;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));
	solve(n);int ans=0;
	for(int i=1;i<=k;i++)ans=(ans+1ll*F[i]*C(k,i)%mod)%mod;
	printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值