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

传送门


解析:

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

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

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

转换一下fi=∑j=1i−1fj=∑j=1i−2fj+fi−1=2fi−1f_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

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

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

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

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

由于帕斯卡三角形上的组合数递推公式Cn+1m=Cnm+Cnm−1C_{n+1}^m=C_n^m+C_n^{m-1}Cn+1m=Cnm+Cnm1。我们把上面这个式子拆开得到∑i=1l(Cri+Cri−1)×2i−1=∑i=1lCri×2i−1+2×∑i=1l−1Cri×2i−1+Cr0×20\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^0i=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)+Cr0×20Q(l,r+1)=Q(l,r)+2\times Q(l-1,r)+C_r^0\times 2^0Q(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)−Crl×2l+Cr0×20Q(l,r+1)=3\times Q(l,r)-C_r^l\times 2^l+C_r^0\times 2^0Q(l,r+1)=3×Q(l,r)Crl×2l+Cr0×20
利用这个式子可以推出r−1r-1r1的情况:Q(l,r−1)=Q(l,r)+Cr−1l×2l−Cr0×203Q(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:

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


代码:

#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;
} 

转载于:https://www.cnblogs.com/zxyoi/p/10047165.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值