QOJ-7650 题解-超级分讨

QOJ-7650

你说得对,但是我常常追忆过去,生命瞬间定格在脑海。我将背后的时间裁剪、折叠、蜷曲,揉捻成天上朵朵白云。

《没有创意的题目名称》

题目简述

给点正整数 \(n (1 \le n \le 2000)\),和一个长度为 \(n+1\) 的序列 \(lim_0, ...... ,lim_n\)。你需要构造一个数组 \(f\),满足:

  • 对于所有 \(0 \le i \le n\),有 \(0 \le f_i \le lim_i (0 \le lim_i \le n)\)
  • 对于每个 \(0 \le i \le n\),对于所有 \(0 \le j\le i\) 存在 \(f_{f_j+f_{i-j}} =f_i\)
  • 对于 \(i > n\)\(f_i=-1\)

询问构造方法的数量,对 \(998244353\) 取模。

问题分析

我们定义 \(i \sim j\) 当且仅当 \(f_i=f_j\)

显然对于每个 \(0 \le i \le n\),对于所有 \(0 \le j\le i\) 存在 \(f_{f_j+f_{i-j}} =f_i\) 可以描述为 对于任意 \(0 \le i \le n,0\le j \le n-i\),满足 \(f_i+f_j \sim i+j\)

显然,题目条件可以等价为下列三个条件。

  • 条件一:对于所有 \(0 \le i \le n\),有 \(0 \le f_i \le lim_i\)
  • 条件二:对于每个 \(0 \le i \le n,0\le j \le n-i\),满足 \(f_i+f_j \le n\)
  • 条件三:对于每个 \(0 \le i \le n,0\le j \le n-i\),满足 \(f_i+f_j \lim i+j\)

\(f\) 两两不同时,\(i \sim j\) 等价为 \(i=j\)。带入 \(i=0,j=0\),存在 \(f_0=0\)。因为 \(f_0+f_i=i\),所以 \(f_i=i\)

假若存在相同元素,另 \(p\) 为最小的满足 \(\exists q,p < q \le n,f_p=f_q\),存在 \(p\sim q \Rightarrow f_p=f_q \Rightarrow f_p+f_1=f_q+f_1 \Rightarrow p+1\sim q+1\)。因此$f_p,f_{p+1},......,f_q $ 为 \(f_p,......,f_n\) 的最小循环节(可能不完整)。

接下来,开始分讨!

1. \(f_0 = 0\)

\(f_0=0\) ,类似于上文,对于 \(0\le i\le p-1,f_i=i\)。类似的,\(p\le i \le q-1,f_i \ge i\)。(考虑带入 \(f_0\),易证)

同时,对于 \(p \le i \le q-1\),存在\(k | f_i-i\)(因为当且仅当 \(i+hk \sim i,h\in \Z\) 时才会成立),推得\(i \equiv f_i \pmod k\)

类似于循环节部分的证明,不难发现此时条件二为条件三的充分条件,因此我们只需要考虑条件二和条件一的约束。

然后,我们发现卡住了……\(q-1\)\(\lfloor n/2 \rfloor\) 的大小关系会影响 \(f\) 的取值,所以继续暴力分讨!

(由于我本地编辑的视角里,如果下面的两个地方用分数会导致重叠,所以用除法表示。)

  1. \(q-1 \le \lfloor n/2 \rfloor\)。此情况下,对于 \(\forall i,f_i \le \lfloor n/2 \rfloor\)

  2. \(q-1 > \lfloor n/2 \rfloor\)。此情况下,对于 \(0\le i \le \lfloor n/2 \rfloor\),类似的,存在 \(f_i=i\)。带入 \(i=1\),能过推得 \(f_i=i(0\le i \le q-1)\)

先枚举 \(k\) 在枚举 \(p\),然后判断 \(q\)\(\lfloor n/2 \rfloor\) 的大小关系即可。可以通过双指针和逆元预处理达到 \(o(n^2)\)

Rep(i,n,0) a[i]=i+k>n?lim[i]:min(a[i+k],lim[i]);//make the true lim
int m=0;
while(a[m]>=m) m++;
if(k<=m)//f_0 == 0
{
    int num=1;
    For(i,1,k-1) modmul(num,(min(a[i],n/2)-i)/k/*f_i=i(mod k)*/+1);
    if((k-1)<=n/2) modadd(res,num);
    //situation 1: f_0 == 0 && q-1 <= n/2
    else modadd(res,1);//situation 2: f_0 == 0 && q-1 > n/2
    for(int p=1;p+k<=min(m,n);p++)
    {
        int q=p+k;
        modmul(num,(min(n/2,a[q-1])-(q-1))/k+1);
        if(q-1<=n/2) modadd(res,num);//situation 1
        else modadd(res,1);//situation 2
        if(p<=n/2) modmul(num,inv[(min(n/2,a[p])-p)/k+1]);
    }
}

2. $f_0 \neq 0 $

此时,存在 \(2f_0 \sim 0\),类似的,不难推得 \(k|2f_0\)

  1. \(k|f_0\),能够推得 $f_0\ge k \Rightarrow k \le \lfloor n/2 \rfloor $。然后加上 \(f_i \equiv i \pmod k\) 即可。直接枚举 \(k\),然后 \(O(n)\) 把每个 \(f_i\) 的取值方案乘起来即可。

    int num=min(a[0],n/2)/k;
    For(i,1,k-1) modmul(num,(min(a[i],n/2)-i)/k+1);
    modadd(res,num);
    
  2. \(k|2f_0\)\(k \nmid f_0\) 时,必然存在 \(2|k\),且 $\frac{k}{2} |f_0 $,不难推得 \(f_0 \equiv \frac{k}{2} \pmod k\)。又有 \(f_0+f_i \sim i\),即 $f_0+f_i \equiv i \pmod k \Rightarrow f_i \equiv i+\frac{k}{2} \pmod k $,代入 \(i=\frac{k}{2}-1\) 时存在 \(f_{\frac{k}{2}-1} \equiv k-1 \pmod k\),由此可得 $f_{\frac{k}{2}-1} \ge k-1 $,又因 \(f_{\frac{k}{2}-1} \ge \lfloor n/2 \rfloor\),得出 $2(k-1) \le n \Rightarrow k-1 \le \lfloor n/2 \rfloor $。
    根据条件二,对于所有 \(0 \le i \le \lfloor n/2\rfloor ,f_i+f_i \sim i\),因此 $ 0\le i \le k \le \lfloor n/2\rfloor,f_i \le \lfloor n/2 \rfloor \Rightarrow 0\le i\le n,f_i \le \lfloor n/2 \rfloor $。因此,这部分与上一部分相同,暴力枚举每个位置的可能取值即可。

     int num=1;
     For(i,0,k-1)
     {
         int l=((k>>1)+i)%k;//min
         if(a[i]<l||n/2<l)
         {
             num=0;
             break;
         }
         modmul(num,(min(a[i],n/2)-l)/k+1);
     }
     modadd(res,num);
    

至此,我们终于讨论完了这道题。总复杂度 \(O(n^2)\),期望得分100。

代码

//I often reminisce about the past
#include<bits/stdc++.h>
using namespace std;
bool Mst;
#define LL long long
#define For(a,b,c) for(int (a)=(b);(a)<=(c);(a)++)
#define Rep(a,b,c) for(int (a)=(b);(a)>=(c);(a)--)
const int N=1e5+10,M=1e6+10,K=30,Mod=998244353;
const int INF=0x3f3f3f3f;
//const LL INF=0x3f3f3f3f3f3f3f3f,all=1ll;
int n,m,k;

template <typename T> void read(T &a){
	char c=getchar();T w=1,f=0;while(c<'0'||c>'9'){if(c=='-') w=-w;c=getchar();}
	while(c>='0'&&c<='9') f=f*10+c-'0',c=getchar();a=f*w;
}
template <typename T> void tomin(T &a,const T &b){if(b<a) a=b;}
template <typename T> void tomax(T &a,const T &b){if(a<b) a=b;}
void modadd(int &a,const int &b,const int &mod=Mod){a+=b;a<0?a+=Mod:a>=Mod?a-=Mod:0;}
void modsub(int &a,const int &b,const int &mod=Mod){a-=b;a<0?a+=Mod:a>=Mod?a-=Mod:0;}
void modmul(int &a,const int &b,const int &mod=Mod){a=1ll*a*b%mod;}
int ksm(int a,int b,int mod=Mod){int res=1;while(b){if(b&1) modmul(res,a,mod);modmul(a,a,mod);b>>=1;}return res;}
int __inv(int x){return ksm(x,Mod-2,Mod);}

int lim[N];
int a[N];
int inv[N];
bool isi[N];

int getlim(int x)
{
	return a[x]+1-x;
}

int Test=1;
void mian()
{
	read(n);
	For(i,0,n) read(lim[i]);
	For(i,2,N-1) inv[i]=__inv(i);inv[1]=1;inv[0]=1;
	isi[0]=1;
	For(i,1,n)
	{
		isi[i]=(lim[i]>=i)&isi[i-1];
	}
	int res=isi[n];
	//if f[0]=0;
	For(k,1,n)
	{
		Rep(i,n,0) a[i]=i+k>n?lim[i]:min(a[i+k],lim[i]);//make the true lim
//		For(i,0,n) printf("%d ",a[i]);puts("");
		int m=0;
		while(a[m]>=m) m++;
		if(k<=m)//f_0 == 0
		{
			int num=1;
			For(i,1,k-1) modmul(num,(min(a[i],n/2)-i)/k/*f_i=i(mod k)*/+1);
			if((k-1)<=n/2) modadd(res,num);//situation 1: f_0 == 0 && q-1 <= n/2
			else modadd(res,1);//situation 2: f_0 == 0 && q-1 > n/2
			for(int p=1;p+k<=min(m,n);p++)
			{
				int q=p+k;
				modmul(num,(min(n/2,a[q-1])-(q-1))/k+1);
				if(q-1<=n/2) modadd(res,num);//situation 1
				else modadd(res,1);//situation 2
				if(p<=n/2) modmul(num,inv[(min(n/2,a[p])-p)/k+1]);
			}
		}
//		printf("k=%d,res=%d->",k,res);
		if(k-1<=n/2)// f_0 != 0
		{
			if(k<=m)//situation 3: f_0 != 0 && f_0 % k == 0
			{
				int num=min(a[0],n/2)/k;
				For(i,1,k-1) modmul(num,(min(a[i],n/2)-i)/k+1);
				modadd(res,num);
			}
			if(k%2==0)//situation 4:f_0 != 0 && f_0 % k != 0
			{
				int num=1;
				For(i,0,k-1)
				{
					int l=((k>>1)+i)%k;//min
					if(a[i]<l||n/2<l)
					{
						num=0;
						break;
					}
					modmul(num,(min(a[i],n/2)-l)/k+1);
				}
				modadd(res,num);
			}
		}
//		printf("%d\n",res);
	}
	cout<<res;//This is a great question!
}
bool Med;
signed main()
{
	cerr<<(&Mst-&Med)/1024.0/1024.0<<" MB\n";
//	freopen("equation.in","r",stdin);
//	freopen("equation.out","w",stdout);
//	cin>>Test;
	while(Test--) mian();
	return 0;
}
posted @ 2026-01-18 14:33  FarrisL  阅读(3)  评论(0)    收藏  举报