zoukankan      html  css  js  c++  java
  • loj3395

    哎,没题解的题真是害人,tm 一个下午就直接砸在这上面了。


    先分析对于某个 (k),哪样的排列会被算。每次操作就是把一些元素抽出来扔到开头或末尾,那么没被动的元素肯定是连续一段区间且保持原顺序不变的。也就是被算的排列一定存在一个长度为 (k) 的上升子段。那么这个条件是否充分呢?容易构造,就反过来把左右的元素塞回去。

    那么就是要求最长上升子段大于等于 (k) 的排列数量。考虑枚举长度为 (k) 的上升子段的位置,但一个合法的排列可能有多个这样的位置,不难想到容斥。就是对每个位置,搞个当前位置的长度等于 (k) 的子段是上升字段的排列集合,那么答案显然就是这些集合的并的大小,那就搞容斥。

    那么对于一个位置集合,把每个位置往后伸 (k),可能形成重叠,那就连起来了,最后变成若干个相连的区间 ([l_i,r_i]),要求每个区间内都上升。这样的排列有多少呢?很简单,就是 (dbinom n{r_1-l_1+1}dbinom{n-(r_1-l_1+1)}{r_2-l_2+1}cdots),就对把没包含在区间内的元素看成单独的区间,给每个区间分配元素。组合数拆一下约一下等于 (dfrac{n!}{prod(r_i-l_i+1)!})

    容易发现这个贡献仅和 ({[l_i,r_i]}) 相关,直接针对位置集合搞反而没前途。于是我们针对这个区间序列搞,贡献是固定的,那就看能够连成这个局势的所有位置集合的容斥系数之和。那显然每个区间是独立的,每个区间内就是要搞若干个位置使得它们恰好重叠形成该区间。这个有点不显然,先暂时设长度为 (i) 的区间的这样的容斥系数之和为 (f_{i,k})。由于每个区间是独立的,每个区间内找出一种方案容斥系数相乘再相加,乘法分配律拆出来发现就是 (prod f_{r_i-l_i+1,k})。那么一个区间序列的贡献就是 (n!proddfrac{f_{r_i-l_i+1,k}}{(r_i-l_i+1)!})

    那么此时不难搞出一个线性 DP 来计算所有区间序列的贡献了。设 (dp_i) 表示考虑到第 (i) 的结果,边界 (dp_0=1),转移方程显然 (dp_i=sumlimits_{j=k}^idfrac{f_{j,k}}{j!}dp_{i-j}),答案 ((1-dp_n)n!)。先假设把所有 (f) 值求出来了,那么这是个分治 FFT 的形式,直接分治 FFT 总复杂度是平方二次对数,求逆是平方对数,可是鄙人不会多项式呀。况且我们 (f) 还没求出来,先求一波再说。

    一个区间设为 ([1,i]),那么 (i-k+1) 肯定是要选的对吧。选了之后,发现只要往前相邻两个位置距离不超过 (k-1) 并且开头在 (1) 就行了。不难列出转移,设 (g_{i,k}=-sumlimits_{j=1}^kg_{i-j,k}),那么 (f_{i,k}=egin{cases}0&i<k\-g_{i-k,k-1}&igeq kend{cases})。那么现在 (f) 求出来了(对 (g) 可以前缀和优化),但还是需要求逆。不妨打个表或者手玩玩 (f),可以发现 (f) 优美的性质(循环节):(k=1) 时除 (i=1) 全为 (0)(由于这种情况特殊,直接输出 (n!) 特判掉);(k>1) 时从 (i=k) 往后每 (k) 个一循环,每个循环节开头两个是 (1,-1),其它都是 (0)。可以发现大量有 (0) 的元素,更具体的,对特定的 (k) 只有 (mathrm O!left(dfrac nk ight)) 个非零元素。那么 DP 转移的时候只需要找非零的地方即可,复杂度分析出来是个调和级数——平方对数的,就可以过了。

    这题模数输入,可以用一下快模,虽然也只快了 400ms,而且不知道有啥意义(((code

    珍爱生命,远离抄袭!
  • 相关阅读:
    A basic Windows service in C++ (CppWindowsService)
    json转换为键值对辅助类
    函数参数复习
    python --------------网络(socket)编程
    计算器
    python------------------异常处理
    hashlib,configparser,logging模块
    面向对象之反射及内置方法
    python之--------封装
    继承进阶
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-loj3395.html
Copyright © 2011-2022 走看看