DTOJ 2022.12.24 测试 题解

(2023 省选模拟 Round #1)

测试成果 50+0+0

太菜了)

A 御神体

这题写了四个多小时,最后还是没写出来ww

莫队一直写挂(不过对莫队的理解加深了很多

题目链接

DTOJ P4346

题目大意

计算出下面这个式子的值,$ q $ 个询问
\(85\%\) 数据,\(\ n,m\leq 10^5,q\leq10^5\)\(15\%\)数据,\(\ n,m\leq 10^6,q\leq10^4\)

\[\left(\sum _{i=0}^{n_i} \sum _{j=0}^{m_i} \binom{i}{j} [i+j \equiv 0 \pmod 2]\right) \bmod {998244353} \]

题解

首先我观察到 \(n=m\) 很好做,答案即为 \(2^n\) 可惜没部分分

接着我注意到 \([i+j \equiv 0 \pmod 2]=\frac{1}{2}(1+(-1)^{i+j})\)

于是式子就可以拆成两部分做

\[\begin{align*} &\sum _{i=0}^{n} \sum _{j=0}^{m} \binom{i}{j} [i+j \equiv 0 \pmod 2]\\ =&\frac{1}{2}\left(\sum _{i=0}^{n} \sum _{j=0}^{m} \binom{i}{j}+(-1)^{i+j}\binom{i}{j}\right)\\ =&\frac{1}{2}\left(\sum _{i=0}^{n} \sum _{j=0}^{m} \binom{i}{j}+\sum _{i=0}^{n}(-1)^{i} \sum _{j=0}^{m} (-1)^{j}\binom{i}{j}\right)\\ =&\frac{1}{2}\left(\sum _{i=0}^{n} \sum _{j=0}^{m} \binom{i}{j}+\sum _{i=1}^{n}(-1)^{i} (-1)^m\binom{i-1}{m}+1\right)\\ =&\frac{1}{2}\left(\sum _{i=0}^{n} \sum _{j=0}^{m} \binom{i}{j}-(-1)^m\sum _{i=0}^{n-1}(-1)^{i} \binom{i}{m}+1\right)\\ \end{align*} \]

注意这边用到了一个组合数小公式

组合数小结论 1

\[\sum_{i=0}^{n}(-1)^i\binom{k}{i}= \begin{cases} (-1)^n\binom{k-1}{n}\quad (k>0)\\ 1 (k=0) \end{cases} \]

因为没注意到 \(k=0\) 所以推错了(还是太菜了ww

证明:

需要一些基本的组合数公式和求和号化简方法(

大家做组合数式子的时候可以画出杨辉三角,比较直观

要小心 \(\binom{n}{m}=\binom{n-1}{m-1}+\binom{n-1}{m}\) 要满足 $m\geq 0 $

\[\begin{align*} &\sum_{i=0}^{n}(-1)^i\binom{k}{i}\\ =&\binom{k-1}{0}+\sum_{i=1}^{n}(-1)^i\left(\binom{k-1}{i-1}+\binom{k-1}{i}\right)\\ =&\sum_{i=0}^{n-1}(-1)^{i+1}\binom{k-1}{i}+\sum_{i=0}^{n}(-1)^i\binom{k-1}{i}\\ =&(-1)^n\binom{k-1}{n}\\ \end{align*} \]

第一部分——求组合数之和

也就是求 \(\sum _{i=0}^{n} \sum _{j=0}^{m} \binom{i}{j}\)

我们发现之前做过一题类似的 洛谷 P5388 [Cnoi2019]最终幻想

就是说组合数的一行的前缀和可以递推

记一行 \(row_{n,m}=\sum_{i=0}^{m}\binom{n}{i}\)

\(row_{n,m}\rightarrow row_{n,m+1}\) 是简单的,只需要加一个 \(\binom{n}{m+1}\)

\(row_{n,m}\rightarrow row_{n+1,m}\) 也不难,画出杨辉三角可以直观地看到 \(row_{n+1,m}=2row_{n,m}-\binom{n}{m}\)

(用 \(\binom{n}{m}=\binom{n-1}{m-1}+\binom{n-1}{m}\) 这个推)

我们来同理做做一列:

\[\begin{align*} &\sum_{i=0}^{n}\binom{i}{m}\\ =&\sum_{i=0}^{n}\left(\binom{i-1}{m}+\binom{i-1}{m-1}\right)\\ =&\sum_{i=0}^{n}\binom{i-1}{m}+\sum_{i=0}^{n}\binom{i-1}{m-1}\\ =&\sum_{i=0}^{n-1}\binom{i}{m}+\sum_{i=0}^{n-1}\binom{i}{m-1}\\ \therefore \binom{n}{m}=&\sum_{i=0}^{n-1}\binom{i}{m-1}\\ \binom{n}{m}+\binom{n}{m-1}=&\sum_{i=0}^{n}\binom{i}{m-1}\\ \sum_{i=0}^{n}\binom{i}{m}=&\binom{n}{m+1}+\binom{n}{m}=\binom{n+1}{m+1} \end{align*} \]

他甚至可以用一个组合数表示

组合数小结论 2

\[\sum_{i=0}^{n}\binom{i}{m}=\binom{n+1}{m+1} \]

于是我们用莫队就可以以 \(O(q\sqrt{m})\) 回答第一部分的询问

第二部分——求组合数一列的交错和

要求这个 \(\sum _{i=0}^{n-1}(-1)^{i} \binom{i}{m}\) 这个长得和蔼可亲的式子

我们记 \(H_{n,m}=\sum _{i=0}^{n}(-1)^{i} \binom{i}{m}\)

首先 \(H_{n,m}\rightarrow H_{n+1,m}\) 跟之前那样只是改了求和上标,只需加上 \((-1)^{n+1}\binom{n+1}{m}\)

\(H_{n,m}\rightarrow H_{n,m+1}\) 我们仿照之前推一列组合数的和的式子:

\[\begin{align*} H_{n,m+1}=&\sum _{i=0}^{n}(-1)^{i} \binom{i}{m+1}\\ =&\sum _{i=0}^{n}(-1)^{i} \binom{i-1}{m+1}+\sum _{i=0}^{n}(-1)^{i}\binom{i-1}{m}\\ =&\sum _{i=0}^{n-1}(-1)^{i+1} \binom{i}{m+1}+\sum _{i=0}^{n-1}(-1)^{i+1}\binom{i}{m}\\ =&-\sum _{i=0}^{n-1}(-1)^{i} \binom{i}{m+1}-\sum _{i=0}^{n-1}(-1)^{i}\binom{i}{m}\\ =&-H_{n,m+1}+(-1)^n\binom{n}{m+1}-H_{n,m}+(-1)^n\binom{n}{m}\\ 2H_{n,m+1}=&(-1)^n\binom{n}{m+1}-H_{n,m}+(-1)^n\binom{n}{m} \end{align*} \]

这就可以递推了

莫队

注意到可以不需要做 m-- 的操作,毕竟对 \(n=m\) 的答案可以直接出来

莫队之前一直没理解,之后看了这个动图就懂了,我觉得脑子里有这样的图就很直观了

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6+5, M = 1e5+5, P = 998244353, fL = 3e3, inv2 = (P+1)>>1;
int fc[N],fci[N],pw2[N]; // 阶乘,阶乘逆元,2的幂
int ksm(int a, int b) { int res=1; for(; b; b>>=1,a=(ll)a*a%P) if(b&1) res=(ll)a*res%P; return res; } 
struct Ayaka { int n,m,ans,id; } rq[M]; // 存每次询问
int C(int n, int m)
{
	if(n<0 or m<0 or n<m) return 0;
	return (ll)fc[n]*fci[m]%P*fci[n-m]%P;
}
inline int pown1(int a) { return (a&1)?-1:1; } // -1 的幂
struct Kurumi
{
	int sum,h,row,col;
	int n,m;
	int query() { return (0ll+sum+1-pown1(m)*h+P)*inv2%P; } //答案可以拆成 sum 和 h 两部分
	void change_n(int d) // d=1/-1 表示加入/删去一行
	{
		if(d<0) sum=(sum-row+P)%P; // 从 sum 删去一行
		if(d>0) row=(2ll*row-C(n,m)+P)%P; // 如上述更新一行
		else row=(ll)(row+C(n-1,m))*inv2%P; // 这个就是上面的式子反过来
		if(d>0) sum=(sum+row)%P; // 往 sum 加入一行
		if(d>0) h=(0ll+h+pown1(n)*C(n,m)+P)%P; // 更新 h
		else h=(0ll+h-pown1(n-1)*C(n-1,m)+P)%P; // 这个就是上面的式子反过来
		n+=d; col=C(n+1,m+1); // 上述结论
	}
	void change_m()
	{
		row=(row+C(n,m+1))%P; col=C(n+1,m+2); sum=(sum+col)%P;
		h=((0ll-h+pown1(n-1)*(C(n-1,m)+C(n-1,m+1)))%P+P)*inv2%P;
		m++;
	}
};
signed main()
{
	fc[0]=1; for(int i=1; i<N; i++) fc[i]=(ll)fc[i-1]*i%P;
	fci[N-1]=ksm(fc[N-1],P-2); for(int i=N-1; i; i--) fci[i-1]=(ll)fci[i]*i%P;
	pw2[0]=1; for(int i=1; i<N; i++) pw2[i]=(pw2[i-1]<<1)%P; // 预处理
	int q; scanf("%d",&q);
	for(int i=1; i<=q; i++) scanf("%d%d",&rq[i].n,&rq[i].m),rq[i].id=i;
	sort(rq+1,rq+q+1, [&] (const Ayaka &a, const Ayaka &b) { if(a.n/fL!=b.n/fL) return a.n<b.n; return a.m<b.m; }); // 莫队分块
	for(int l=1,r; l<=q; l=r+1)
	{
		r=l; while(r<q and rq[r+1].m>=rq[r].m) r++; // 这边记得是 >= 不然会 tle
		int tmp=rq[l].m; Kurumi cur={pw2[tmp+1]-1,0,pw2[tmp],1,tmp,tmp}; // n=m 初始情况
		for(int i=l; i<=r; i++)
		{
			while(cur.m<rq[i].m) cur.change_m();
			while(cur.n<rq[i].n) cur.change_n(1);
			while(cur.n>rq[i].n) cur.change_n(-1);
			rq[i].ans=cur.query();
		}
	}
	sort(rq+1,rq+q+1, [&] (const Ayaka &a, const Ayaka &b) { return a.id<b.id; }); 
	for(int i=1; i<=q; i++) printf("%d\n",rq[i].ans); 
	return 0;
}

posted @ 2022-12-24 17:53  copper_carbonate  阅读(99)  评论(1编辑  收藏  举报