CF1967E. Again Counting Arrays

非常好的反射容斥基础练习题,可以检测对反射容斥原理是否完全掌握。

在阅读本篇题解前,请确保你已经会了有两边限制的反射容斥(对应例题:P3266 [JLOI2015] 骗我呢)。

题目链接:E2 - Again Counting Arrays

题目大意:给定 \(n\)\(m\)\(b_0\),我们认为一个非负数组 \(b\) 是好的,当且仅当对于所有 \(1\le i\le n\),同时满足 \(|b_i-b_{i-1}|=1\)\(a_i\neq b_i\)。要求统计有多少个长度为 \(n\)\(a\) 数组,使得 \(1\le a_i\le m\),至少存在一个符合指定 \(b_0\) 值的非负 \(b\) 数组。答案对 \(998244353\) 取模,有多测。\(n,m,b_0\le 2\times 10^6\)\(\sum n\le 10^7\)

分析性质并转化题意

首先不难发现,若没有非负的限制,则一定存在合法的 \(b\),因为无论如何在填下一个数的时候都可以有两种选择。

那么如果非法,就一定是填到后面出现了 \(b_i=0,a_{i+1}=1\) 的情况,所以要尽量让 \(b\) 往上走。这时可以发现,若存在合法的 \(b\),那么贪心地尽量往上走一定可行

  • 证明考虑反证法,若贪心非法,假设存在另一个不贪心的 \(b'\) 是合法的。那么最后一处不同的位置一定是 \(b'\) 往下走,\(b\) 往上走,那就会有 \(b'< b\),而 \(b\) 最后是会走到 \(-1\) 的,故产生矛盾

那么我们就按照贪心的策略填 \(b\),就能得到 \(a,b\) 之间的一一对应,转而考虑对序列 \(b\) 进行计数。

朴素 DP

DP 设 \(f_{i,j}\) 表示 \(b_i=j\) 的方案数,有如下三种转移:

  • \((m-1)f_{i,j} \to f_{i+1,j+1}\)
  • \(f_{i,j}[0< j< m] \to f_{i+1,j-1}\)
  • \(m \cdot f_{i,m} \to f_{i+1,m}\)

这一眼就很格路计数,我们考虑分析其组合意义。

路径计数

此时问题转化为了对满足如下条件的路径进行计数:

  • 起点为 \((0,b_0)\)
  • 每次有 \(1\) 种方案往右下,有 \(m-1\) 种方案往右上走
  • 路径上所有点纵坐标都必须 \(\ge 0\),若纵坐标到 \(m\) 则结束,横坐标到 \(n\) 也要结束

于是考虑枚举结束位置 \((i,m),(n,i)\),由此可以得出往右上、右下分别走了多少次,就变成对过程中不越过 \(y=0,y=m-1\) 的路径进行计数。这部分采用 P3266 的经典反射容斥即可完成。

分析时间复杂度,每次反射容斥的复杂度都是 \(O(\frac{n}{m})\) 的,所以算出来是 \(O((n+m)\frac{n}{m})\),比较爆炸。

但是注意到之前的朴素 DP 是 \(O(nm)\) 的,所以可以根号分治一下平衡到根号复杂度,能够通过 Easy Version。

进一步优化

观察反射容斥部分,发现复杂度爆炸的因素是在枚举 \((i,m)\) 上,考虑继续挖掘组合意义。

在到达 \(y=m\) 之后,DP 转移上是每次有 \(m\) 的方案数继续在 \(y=m\) 上走。这里我们就假装他还是有 \(1\) 种方案往右下,有 \(m-1\) 种方案往右上走,并在之后去除掉纵坐标 \(\ge 0\) 的限制。

那么就变成枚举最后走到 \((n,j)\),并计算先越过 \(y=m-1\) 之后,到达对应位置的方案数。这里仍然是反射容斥,对应限制是越过 \(y=m-1\) 之前不得越过 \(y=0\),这一看就是反射容斥的子问题。

虽然说这里还是要对 \(O(n)\) 个终点做反射容斥,但和之前不同的是,现在终点都被固定在 \(x=n\) 上了,我们考虑去维护每个 \(\binom{n}{i}\) 对答案的贡献系数。或者说,去维护每个终点对应组合数的贡献系数。

具体的,每次反射容斥时,我们都是把终点对称到某位置 \((n,y)\) 上,然后去计算对应的组合数乘 \((-1)^o(m-1)^p\)。而每次被对称到的点都可以被拆分成两个公差为 \(2m+2\) 的等差数列,两个等差数列对应的贡献系数相同,那么就能差分维护。这样就能实现线性求解。

实现细节

推式子时要特别注意,普通反射容斥是分别计算 R,RL,RLR,RLRL,...,但是我们要依次计算的是 R,LR,RLR,LRLR,...,所以不能直接对着单个递归的函数去推。本人就是因为太久没做反射容斥题把这玩意忘了,对着前者对称到的坐标去推,直接虚空调试了 3h。

对于终点 \((n,i)\) 的位置,其实会有一个分类讨论。若 \(i\ge m\),说明能保证到过 \(y=m\),这里只需依次 -L +RL -LRL ...。而 \(i\lt m\) 时,就需要先沿直线 \(y=m\) 对称到 \(2m-i\),再进行上述容斥。

关于在等差数列上加一个定值,其实只需要在差分数组的起点处打标记即可,因为一定是对称到无法到达的地方为止。

另外,注意到正值的贡献一定是往右累计的,负值的贡献一定是往左累计的,所以可以两个情况各开一个数组,最后再加起来。虽然其实是可以做到塞一个数组里的,但是本人懒得推分界线所以就还是拆开了。

实际上是可以把本来终点就在 \((n,i)\) 上的路径也合进来一块计数的,但本人懒得继续推式子就把这一部分仍然用朴素的反射容斥去做掉了,所以常数会比较大。

#include<bits/stdc++.h>
using namespace std;
#define N 2000010
#define LL long long
#define MOD 998244353
LL n,m,b,p[N],q[N],ans,s,c[N<<2],d[N<<2],pm[N];
LL C(LL n,LL m){return p[n]*q[m]%MOD*q[n-m]%MOD;}
LL qow(LL x,LL y){return y?(y&1?x*qow(x,y-1)%MOD:qow(x*x%MOD,y/2)):1;}
//b->x
//L+R=n,R-L=x-b
LL L(LL l,LL r,LL x,LL n)
{
	if(abs(x-b)>n)return 0;
	return (C(n,(n+x-b)/2)+MOD-L(2*l-2-r,l-2,2*l-2-x,n))%MOD;
}
LL R(LL l,LL r,LL x,LL n)
{
	if(abs(x-b)>n)return 0;
	return (C(n,(n+x-b)/2)+MOD-R(r+2,2*r+2-l,2*r+2-x,n))%MOD;
}
void addC(LL x,LL w){c[x+N]=(c[x+N]+w)%MOD;}
void addD(LL x,LL w){d[x+N]=(d[x+N]+w)%MOD;}
int main()
{
	p[0]=p[1]=q[0]=q[1]=1;
	for(LL i=2;i<N;i++){
		p[i]=p[i-1]*i%MOD;
		q[i]=(MOD-MOD/i)*q[MOD%i]%MOD;
	}
	for(LL i=2;i<N;i++)q[i]=q[i]*q[i-1]%MOD;
	int T;
	scanf("%d",&T);
	while(T--){
		ans=0;
		scanf("%lld%lld%lld",&n,&m,&b);
		if(b>=m || b>=n){
			printf("%lld\n",qow(m,n));
			continue;
		}
		pm[0]=1;
		for(LL i=1;i<=n;i++)pm[i]=pm[i-1]*(m-1)%MOD;
		for(LL i=0;i<m;i++)if(!((b^n^i)&1) && b-n<=i && i<=b+n)
			ans=(ans+(L(0,m-1,i,n)+R(0,m-1,i,n)+MOD-C(n,(n+i-b)/2))*pm[(n+i-b)/2])%MOD;
//		for(LL i=1;i<=n;i++)if(!((b^i^m)&1) && abs(m-1-b)<=i-1)
//			ans=(ans+(L(0,m-1,m-1,i-1)+R(0,m-1,m-1,i-1)+MOD-C(i-1,(m-2+i-b)/2))*pm[(m+i-b)/2]%MOD*qow(m,n-i))%MOD;

		//(0,b)->(n,i)   R=(n+i-b)/2
		//i<m i->2m-i  -2-i->-2-2m+i
		//L (0,m-1,i,n) -(-m-1,-2,-2-i,n) (-2m-2,-m-3,-2m-2+i,n) -(-3m-3,-2m-4,-2m-4-i,n)
		//R +(0,m-1,i,n) (m+1,2m,2m-i,n) +(2m+2,3m+1,2m+2+i,n) (3m+3,4m+2,4m+2-i,n)
		//i>=m
		//L (0,m-1,i,n) -(-m-1,-2,-2-i,n) (-2m-2,-m-3,-2m-2+i,n) -(-3m-3,-2m-4,-2m-4-i,n)
		//R +(0,m-1,i,n) (m+1,2m,2m-i,n) +(2m+2,3m+1,2m+2+i,n) (3m+3,4m+2,4m+2-i,n)
		
		for(LL i=b-n;i<=b+n;i+=2)c[i+N]=d[i+N]=0;
		for(LL i=b-n;i<m;i+=2){
			if(abs(2*m-i-b)<=n)addC(2*m-i,pm[(n+i-b)/2]);
			if(abs(b+2*m+2-i)<=n)addD(-2*m-2+i,pm[(n+i-b)/2]);
		}
		for(LL i=b+n;i>=m;i-=2){
			addC(i,pm[(n+i-b)/2]);
			if(abs(b+2+i)<=n)addD(-2-i,pm[(n+i-b)/2]);
		}
		LL l=2*m+2;
		for(LL i=b-n+l;i<=b+n;i+=2)c[i+N]=(c[i+N]+c[i+N-l])%MOD;
		for(LL i=b+n-l;i>=b-n;i-=2)d[i+N]=(d[i+N]+d[i+N+l])%MOD;
		for(LL i=b-n;i<=b+n;i+=2)ans=(ans+C(n,(n+i-b)/2)*(c[i+N]+MOD-d[i+N]))%MOD;
		printf("%lld\n",ans);
	}
}
posted @ 2026-07-31 04:17  DeaphetS  阅读(4)  评论(0)    收藏  举报