并不对劲的bzoj5342:loj2554:uoj401:p4566: [Ctsc2018]青蕈领主
题目大意
\(T\)(\(T\leq100\))组询问
有\(1\)到\(n\)(\(n\leq50000\))这\(n\)个整数组成的一个排列
定义这个排列的一个子区间是“连续”的,当且仅当这个子区间在位置上和在值域上都是连续的
分别给出这个排列以每个位置\(i\)为右端点的最长“连续”子区间的长度\(l_i\),问有多少个排列满足这个条件
题解
发现这些最长“连续”子区间一定是相互包含或相离的,不会相交
用反证法:假设有\(x<y\),\(x-l_x+1< y-l_y+1\)且\(y-l_y+1< x\),那么\(x-l_x+1\)到\(x\)这一段和\(y-l_y+1\)到\(y\)这一段在值域上连续,那么\(x-l_x+1\)到\(y-l_y+1\)、\(y-l_y+1\)到\(x\)、\(x\)到\(y\)这三段能拼成值域上连续的一段,这样\(x-l_x+1\)到\(y\)就是连续的,以\(y\)为右端点的最长“连续”子区间长度至少\(y-x+l_x\),显然\(y-x+l_x>l_y\),与题意不符
这样就可以将每个位置按以该位置为右端点的最长“连续”子区间的包含关系建成一棵树,而且判断无解也变得十分简单,当有相交的最长“连续”子区间或\(l_n\neq n\)时无解
建出树后,会发现对于每个点\(x\),\(x\)对应的区间相当于每个儿子对应的区间和位置\(x\)上的数拼成的
因为\(x\)的儿子对应的区间都是“连续”的,所以可以把每个儿子看成一个点,点\(x\)的子树的方案数就是所有儿子的方案数乘以把每个儿子看成一个点后满足某些性质的排列数
“满足某些性质的排列”指一些除了整个排列是“连续”的以外,任何一个长度大于\(1\)的子区间都不“连续”的排列,称满足该性质的排列是合法排列
设\(f_i\)表示长度为\(i+1\)的合法排列数
手算可得,\(f_0=1\),\(f_1=2\)
考虑递推求\(f_i\),即已有一个由\(1\)到\(i\)组成的排列,加入\(i+1\),使它合法
有这样两种可能:
一、\(1\)到\(i\)组成的排列合法
\(i+1\)只要不放到\(i\)旁边就行,\(1\)到\(i\)组成的排列一共有\(i+1\)个缝隙,去掉与\(i\)相邻的两个,还剩\(i-1\)个
所以这一部分是\((i-1)*f_{i-1}\)个
二、\(1\)到\(i\)组成的排列不合法
会发现加入\(i+1\)顶多将一个多余的“连续”子区间“打断”,那么就可以看成求有一个长度为\(i\)的排列,该排列只有一个长度大于\(1\)的“连续”子区间,且该子区间内部没有“连续”子区间(即该子区间合法),将\(i+1\)放到这个“连续”合法子区间内的一个位置把这个“连续”合法子区间“打断”
假设“连续”子区间长度为\(j\)
会发现这个问题非常不好解决,那么可以从结果来倒着想:
从这个排列中去掉\(i+1\)后,这个排列只有一个子区间是“连续”的,那么将该子区间缩成一个点,整个排列就变成了一个长度为\(i-j+1\)的合法排列
再把这个过程正过来,就变成了:
(1)生成一个长度为\(i-j+1\)的合法排列
(2)选择一个点,不能选\(i-j+1\)或\(i-j\)
(3)让被选的点变成一个长度为\(j\)的“连续”排列,再加入\(i+1\)这个数——会发现这个过程难以计算,那么还是从结果考虑——结果是一个长度为\(j+1\)的由\(1\)到\(j\)和\(i+1\)合法排列,它去掉\(i+1\)后一定是个长度为\(j\)的“连续”合法子区间(一个长度为\(j\)的由\(1\)到\(j\)的一定是“连续”的)——那么完全可以看成生成一个长度为\(j+1\)的合法排列
这个过程每一步的影响是:
(1)长度为\(i-j+1\)的合法排列数,\(f_{i-j}\)
(2)在\(i-j-1\)个数中选一个,\(i-j-1\)
(3)长度为\(j+1\)的合法排列数,\(f_j\)
在加入\(i+1\)前,“连续”子区间长度为\(j\)的长度为\(i+1\)的合法排列数为\((i-j-1)*f_j*f_{i-j}\)
\(j\)的取值范围是多少呢?
要想使\(1\)到\(i\)组成的排列不合法,多余的“连续”子区间的长度\(j\)至少为\(2\)
要想使\(i-j-1\)个可选的数中至少有一个,\(i-j-1\geq1\),即\(j\leq i-2\)
所以这一部分是$$\sum_{j=2}^{i-2}{(i-j-1)f_jf_{i-j}}$$
这两部分一共有$$f_i=(i-1)f_{i-1}+\sum_{j=2}^{i-2}{(i-j-1)f_j*f_{i-j}}$$
现在想必是可以进行\(\Theta(n^2)\)的递推了,能否进行优化呢?
后面的部分分治FFT解决,前面的部分在分治到左端点等于右端点时直接加到下一项就行
这样就完了?想必没有
会发现在计算完\([l,m]\)这一段后,可能会出现\(i\in[m+1,r],j\in[l,m]\),使\(i-j\in[m+1,r]\),显然此时\(f_{i-j}\)的值还未算出来
这时就得把后面的部分拆成两部分:
这就能卷了吧?
#include<algorithm>
#include<cmath>
#include<complex>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#define rep(i,x,y) for(register int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(register int i=(x);i>=(y);--i)
#define maxn 50010
#define maxlen (maxn<<2)
#define LL long long
#define mi (L+R>>1)
#define lim 80
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x*f;
}
void write(int x)
{
if(x==0){putchar('0'),putchar('\n');return;}
int f=0;char ch[20];
if(x<0)putchar('-'),x=-x;
while(x)ch[++f]=x%10+'0',x/=10;
while(f)putchar(ch[f--]);
putchar('\n');
return;
}
int a[maxlen],b[maxlen],c[maxlen],r[maxlen],lena,lenb,lenc,len,f[maxn],no;
int t,n,l[maxn];
const LL mod=998244353;
int mul(int x,int y){int ans=1;while(y){if(y&1)ans=(LL)ans*(LL)x%mod;x=(LL)x*(LL)x%mod;y>>=1;}return ans;}
void dnt(int *u,int f)
{
rep(i,1,lenc)if(i<r[i])swap(u[i],u[r[i]]);
for(int i=1;i<lenc;i<<=1)
{
int wn=mul(3,(mod-1)/(i<<1));
if(f==-1)wn=mul(wn,mod-2);
for(int j=0;j<lenc;j+=(i<<1))
{
int w=1,x,y;
rep(k,0,i-1)
x=u[j+k],y=(LL)w*(LL)u[i+j+k]%mod,u[j+k]=(x+y)%mod,u[j+i+k]=(x-y+mod)%mod,w=(LL)w*(LL)wn%mod;
}
}
if(f==-1)
{
int inv=mul(lenc,mod-2);
rep(i,0,lenc-1)u[i]=(LL)u[i]*(LL)inv%mod;
}
}
void work(int L,int R)
{
if(L==R)
{
if(L==1)return;
f[L]=(f[L]+(LL)f[L-1]*(LL)(L-1)%mod)%mod;
f[L]=(f[L]-(LL)(L-2)*(LL)f[L-1]%mod*(LL)f[1]%mod+mod)%mod;
return;
}
work(L,mi);
if(R-L<=lim)
{
rep(i,mi+1,R)
{
rep(j,i-mi,min(mi,i-L))
f[i]=((LL)f[i]+(LL)f[j]*(LL)f[i-j]%mod*(LL)(j-1)%mod)%mod;
rep(j,i-mi,min(i-L,L-1))
f[i]=((LL)f[i]+(LL)f[j]*(LL)f[i-j]%mod*(LL)(i-j-1)%mod)%mod;
}
}
else
{
len=lena=lenb=0;lenc=1;
rep(i,1,min(R-L,mi))a[lena++]=(LL)(i-1)*(LL)f[i]%mod;
rep(i,L,mi)b[lenb++]=f[i];
while(lenc<lena+lenb-1)lenc<<=1,len++;
rep(i,1,lenc)r[i]=(r[i>>1]>>1)|((i&1)<<(len-1));
dnt(a,1),dnt(b,1);
rep(i,0,lenc-1)c[i]=(LL)a[i]*(LL)b[i]%mod;
dnt(c,-1);
rep(i,mi+1,R)f[i]=(f[i]+c[i-L-1])%mod;
rep(i,0,lenc)a[i]=b[i]=c[i]=0;
len=lena=lenb=0;lenc=1;
rep(i,1,min(R-L,L-1))a[lena++]=f[i];
rep(i,L,mi)b[lenb++]=(LL)(i-1)*(LL)f[i]%mod;
while(lenc<lena+lenb-1)lenc<<=1,len++;
rep(i,1,lenc)r[i]=(r[i>>1]>>1)|((i&1)<<(len-1));
dnt(a,1),dnt(b,1);
rep(i,0,lenc-1)c[i]=(LL)a[i]*(LL)b[i]%mod;
dnt(c,-1);
rep(i,mi+1,R)f[i]=(f[i]+c[i-L-1])%mod;
rep(i,0,lenc)a[i]=b[i]=c[i]=0;
}
work(mi+1,R);
}
int getans(int L,int R)
{
int ans=1,nums=0;
dwn(i,R-1,L)
{
int x=i-l[i]+1;
if(x<L){no=1;return 0;}
int tmp=getans(x,i);
if(no==1)return 0;
ans=(LL)ans*(LL)tmp%mod;
nums++; i=x;
}
ans=(LL)ans*(LL)f[nums]%mod;
return ans;
}
int main()
{
t=read(),n=read();
f[0]=1;
f[1]=2;
work(2,n);
while(t--)
{
no=0;
rep(i,1,n)l[i]=read();
if(l[n]!=n){puts("0");continue;}
int ans=getans(1,n);
write(ans);
}
return 0;
}