2018.10.24【NOIP练习】Leo的组合数问题(组合数学)(莫队)

传送门


解析:

总是忍不住要吐槽一句,这题面也太长了吧。。。

首先,肯定要把我们要求的东西推成一个式子,不然怎么可能做题。。。

f i f_i fi表示当前以 i i i为最大值的方案数。
那么 i i i之后的数显然是单调递减且连续,方案数为 1 1 1
考虑前 i − 1 i-1 i1个位置,放法数就是 ∑ j = 1 i − 1 f j \sum_{j=1}^{i-1}f_j j=1i1fj
所以我们现在得到递推式 f i = ∑ j = 1 i − 1 f j f_i=\sum_{j=1}^{i-1}f_j fi=j=1i1fj f 1 = 1 f_1=1 f1=1

转换一下 f i = ∑ j = 1 i − 1 f j = ∑ j = 1 i − 2 f j + f i − 1 = 2 f i − 1 f_i=\sum_{j=1}^{i-1}f_j=\sum_{j=1}^{i-2}f_j+f_{i-1}=2f_{i-1} fi=j=1i1fj=j=1i2fj+fi1=2fi1

所以得到通项公式 f i = 2 i − 1 f_i=2^{i-1} fi=2i1

那么询问的答案就是 Q ( n , m ) = ∑ i = 1 n C m i × 2 i − 1 Q(n,m)=\sum_{i=1}^{n}C_{m}^i\times 2^{i-1} Q(n,m)=i=1nCmi×2i1
数据范围 1 e 5 1e5 1e5,果断莫队。
l = n , r = m l=n,r=m l=n,r=m
我们只需要找到根据 l , r l,r l,r更新答案的较小复杂度方法就行了
幸运的是,这个式子在 l ± 1 l\pm 1 l±1 r ± 1 r \pm 1 r±1的时候都有 O ( 1 ) O(1) O(1)的转移方式。

首先 l l l的转移:
Q ( l − 1 , r ) = Q ( l , r ) − C r l × 2 l − 1 Q(l-1,r)=Q(l,r)-C_r^l\times 2^{l-1} Q(l1,r)=Q(l,r)Crl×2l1 Q ( l + 1 , r ) = Q ( l , r ) + C r l + 1 × 2 l Q(l+1,r)=Q(l,r)+C_r^{l+1}\times 2^{l} Q(l+1,r)=Q(l,r)+Crl+1×2l
这个转移十分显然,就是直接根据表达式转移。

r r r的转移需要一点技巧:
怎么求 Q ( l , r + 1 ) = ∑ i = 1 l C r + 1 i × 2 i − 1 Q(l,r+1)=\sum_{i=1}^lC_{r+1}^{i}\times 2^{i-1} Q(l,r+1)=i=1lCr+1i×2i1

由于帕斯卡三角形上的组合数递推公式 C n + 1 m = C n m + C n m − 1 C_{n+1}^m=C_n^m+C_n^{m-1} Cn+1m=Cnm+Cnm1。我们把上面这个式子拆开得到 ∑ i = 1 l ( C r i + C r i − 1 ) × 2 i − 1 = ∑ i = 1 l C r i × 2 i − 1 + 2 × ∑ i = 1 l − 1 C r i × 2 i − 1 + C r 0 × 2 0 \sum_{i=1}^{l}(C_r^i+C_r^{i-1})\times 2^{i-1}=\sum_{i=1}^{l}C_r^i\times 2^{i-1}+2\times\sum_{i=1}^{l-1}C_r^{i}\times2^{i-1}+C_r^0\times 2^0 i=1l(Cri+Cri1)×2i1=i=1lCri×2i1+2×i=1l1Cri×2i1+Cr0×20

所以其实就是 Q ( l , r + 1 ) = Q ( l , r ) + 2 × Q ( l − 1 , r ) + C r 0 × 2 0 Q(l,r+1)=Q(l,r)+2\times Q(l-1,r)+C_r^0\times 2^0 Q(l,r+1)=Q(l,r)+2×Q(l1,r)+Cr0×20
把刚才退出来的 Q ( l − 1 , r ) Q(l-1,r) Q(l1,r)的式子代进来得到
Q ( l , r + 1 ) = 3 × Q ( l , r ) − C r l × 2 l + C r 0 × 2 0 Q(l,r+1)=3\times Q(l,r)-C_r^l\times 2^l+C_r^0\times 2^0 Q(l,r+1)=3×Q(l,r)Crl×2l+Cr0×20
利用这个式子可以推出 r − 1 r-1 r1的情况: Q ( l , r − 1 ) = Q ( l , r ) + C r − 1 l × 2 l − C r 0 × 2 0 3 Q(l,r-1)=\frac{Q(l,r)+C_{r-1}^l\times 2^l-C_r^0\times 2^0}{3} Q(l,r1)=3Q(l,r)+Cr1l×2lCr0×20

那么就可以上莫队了。

trick:

注意能将 r r r变大的时候尽量先移动 r r r,否则先移动 l l l,避免转移经过非法组合数就 g g gg gg了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define re register
#define gc getchar
#define pc putchar
#define cs const
 
inline int getint(){
	re int num;
	re char c;
	while(!isdigit(c=gc()));num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
}

inline void outint(long long a){
	static char ch[23];
	if(a==0)pc('0');
	while(a)ch[++ch[0]]=a-a/10*10,a/=10;
	while(ch[0])pc(ch[ch[0]--]^48);
}

cs int N=100005;
cs int mod=19260817;

int fac[N],inv[N],ifac[N];
int pow2[N];
inline int C(int n,int m){
	return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int l=1,r=1,now=1;
inline void ll(){now=(1ll*now-1ll*C(r,l)*pow2[l-1]%mod+mod)%mod;--l;}
inline void lr(){now=(1ll*now+1ll*C(r,l+1)*pow2[l]%mod)%mod;++l;}
inline void rl(){now=(1ll*now+1ll*C(r-1,l)*pow2[l]%mod-1ll*C(r,0)*pow2[0]%mod+mod)%mod*inv[3]%mod;--r;}
inline void rr(){now=(3ll*now-1ll*C(r,l)*pow2[l]%mod+1ll*C(r,0)*pow2[0]%mod+mod)%mod;++r;}

struct Query{int l,r,id;}Q[N];
int ans[N],block[N],B,bcnt;
inline bool cmp(cs Query &a,cs Query &b){
	if(block[a.l]^block[b.l])return block[a.l]<block[b.l];
	return (block[a.l]&1)^(a.r>b.r);
}
int n,m,q;
int maxn;
signed main(){
	fac[0]=fac[1]=inv[0]=inv[1]=ifac[0]=ifac[1]=pow2[0]=1;
	pow2[1]=2;
	for(int re i=2;i<N;++i){
		fac[i]=1ll*fac[i-1]*i%mod;
		inv[i]=1ll*(mod-mod/i)*inv[mod-mod/i*i]%mod;
		ifac[i]=1ll*ifac[i-1]*inv[i]%mod;
		pow2[i]=(pow2[i-1]<<1)%mod;
	}
	q=getint();
	for(int re i=1;i<=q;++i){
		Q[i].id=i;
		Q[i].r=getint();
		Q[i].l=getint();
		maxn=max(Q[i].r,maxn);
	}
	B=sqrt(maxn);bcnt=1;
	for(int re i=1;i<=maxn;++i){
		block[i]=bcnt;
		if(i%B==0)++bcnt;
	}
	sort(Q+1,Q+q+1,cmp);
	for(int re i=1;i<=q;++i){
		if(Q[i].r>=r){
			while(r<Q[i].r)rr();
			while(r>Q[i].r)rl();
			while(l<Q[i].l)lr();
			while(l>Q[i].l)ll();
		}
		else{
			while(l<Q[i].l)lr();
			while(l>Q[i].l)ll();
			while(r>Q[i].r)rl();
			while(r<Q[i].r)rr();
		}
		ans[Q[i].id]=now;
	}
	for(int re i=1;i<=q;++i)outint(ans[i]),pc('\n');
	return 0;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值