loj6402 校门外的树(dp,多项式求逆)
庆祝一下,,,第一个我自己做出来的,,,多项式的题(没办法,我太弱
虽然用了2个小时才想出来,但这毕竟是0的突破……
首先声明,虽然我写的题解很长,但是大部分都是证明和废话
第一步,转化题意(用时30min):
一个1~n的全排列,如果存在,那么就把和之间连一条边。
问所有p的所有排列方案,最后联通块的大小的乘积,的和。
因为原题中是随机的实数,所以说我们可以不用考虑有的情况。
因为最后用到的只有相对的大小关系,所以可以直接转化成一个1~n的排列。
其实很多时间都花在"诶,是这个意思吗,不会啊","哦,不对,看错题了"。
第二步,找规律推性质(用时50min):
我们发现,一个联通块,一定是到连续的一段。
假如说,和在同一个联通块内(令),存在满足,并且不在和所在联通块内。
我们来证明这种情况是不存在的。
首先,,否则与、有直接的边相连。
那么,但是他们在同一个联通块内,
所以一定存在一个位置满足或者
那么一定与直接相连,所以证出矛盾。
同理,我们可以证到一个联通块的也是连续的一段。
但是我不是这么找到这个性质的,因为我上面的方法只是证明了这个性质,但是如果我猜不到这个性质……
我按照套路思考,每个联通块找一个代表员好了,找谁呢,那就最小的那个吧。
我们把点放在一个平面上,第个点的坐标是。
我们按照从小到大,加入每个点。
首先,最小的那个点加入的时候,我们考虑找它所在的联通块的点。
找到一个满足的最大的,那么到这个连续区间的所有点都在同一个联通块内。
(把这些点分成,两种情况讨论就可以了)
然后的一些点,也可能在这个联通块内。
然后我们把这些点删掉,再找到最小的那个点,递归下去。
我们发现,我们把一个n*n的矩形,一次一次的减去一个联通块所在的"势力范围",其实是一次一次砍掉了右边的一条和下面的一条,剩下的还是一个矩形。
如图:
其中淡粉色是一个联通块的势力范围,是这个联通块最大的点,是这个联通块最靠左(就是编号最小)的点。
我们再找下一个联通块的时候,下一个联通块势力范围一定是在白色部分。
所以下一个联通块的最小的点的一定有,编号最大的点
于是我们两个性质都找到啦。
有了这两个性质之后,我们可以发现,一个联通块,一定满足:一定是的一个排列。
同时一定是的一个排列。
同时中不存在满足是的一个排列,否则就可以分出两个联通块了。
也就是说,如果我们称满足是的一个排列的是好的,那么每个都一定是一个联通块的右端点。
第三步,推式子(用时40min):
有了上面的几个性质,我们可以开始列方程了。
我们令表示1~n的一个排列,除位置外,不存在其他位置是好的,的方案数。
那么
令,特别地,我们规定
那么
把移到左边,可以得到
我们发现这是一个卷积形式。(因为)
令表示前个数,并且是一个联通块右端点,的贡献。规定
枚举上一个联通块的右端点,那么有:
令。
又是一个卷积形式。(因为)
于是两次多项式求逆就可以解决问题啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | //Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) const ll mod=998244353,B=3; const int maxn=1e7+7; ll n,g[maxn],h[maxn],ginv[maxn],hinv[maxn],X[maxn]; template < typename T> void read(T& aa) { aa=0; char cc= getchar ();T ff=1; while ((cc!= '-' )&&(cc< '0' ||cc> '9' )) cc= getchar (); if (cc== '-' ) ff=-1,cc= getchar (); while (cc>= '0' &&cc<= '9' ) aa=aa*10+cc- '0' ,cc= getchar (); aa*=ff; } ll qp(ll x,ll k) { ll rs=1; while (k) { if (k&1) rs=rs*x%mod; k>>=1; x=x*x%mod; } return rs; } ll finv(ll x) { return qp(x,mod-2);} ll qp1(ll x,ll k) { if (k<0) return qp(finv(x),-k); return qp(x,k); } void Rader(ll F[],ll len) { for ( int i=1,j=len>>1,k;i<len-1;++i) { if (i<j) swap(F[i],F[j]); k=len>>1; while (j>=k) {j-=k;k>>=1;} if (j<k) j+=k; } } void FFT(ll F[],ll len,ll on) { Rader(F,len); for ( int h=2;h<=len;h<<=1) { ll wn=qp1(B,(mod-1)*on/h); for ( int j=0;j<len;j+=h) { ll w=1; for ( int i=j;i<j+h/2;++i) { ll u=F[i],v=w*F[i+h/2]%mod; F[i]=(u+v)%mod; F[i+h/2]=(u-v+mod)%mod; w=w*wn%mod; } } } ll x=finv(len); if (on==-1) For(i,0,len) F[i]=F[i]*x%mod; } void get_inv(ll N,ll* A,ll* B) { if (N==1) { B[0]=finv(A[0]); return ; } get_inv((N+1)>>1,A,B); ll n=1; for (;n<=(N<<1);n<<=1); For(i,0,N-1) X[i]=A[i]; For(i,N,n) X[i]=B[i]=0; FFT(X,n,1); FFT(B,n,1); For(i,0,n) B[i]=B[i]*(2-B[i]*X[i]%mod+mod)%mod; FFT(B,n,-1); For(i,N,n) B[i]=0; } int main() { read(n); g[0]=1; For(i,1,n) g[i]=g[i-1]*(ll)i%mod; get_inv(n+1,g,ginv); For(i,1,n) h[i]=ginv[i]*(ll)i%mod; h[0]=1; get_inv(n+1,h,hinv); printf ( "%lld\n" ,hinv[n]); return 0; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 聊一聊 操作系统蓝屏 c0000102 的故障分析
· SQL Server 内存占用高分析
· .NET Core GC计划阶段(plan_phase)底层原理浅谈
· .NET开发智能桌面机器人:用.NET IoT库编写驱动控制两个屏幕
· 用纯.NET开发并制作一个智能桌面机器人:从.NET IoT入门开始
· 我干了两个月的大项目,开源了!
· 推荐一款非常好用的在线 SSH 管理工具
· 聊一聊 操作系统蓝屏 c0000102 的故障分析
· 千万级的大表,如何做性能调优?
· .NET周刊【1月第1期 2025-01-05】