agc055F - Creative Splitting 题解

又是 atc 风格 DP/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu/tuu


首先容易想到对一个序列 p1nk 搞一个贪心的判断方法。

我一开始想的是:维护 n 个序列,初始为空。从左往右考虑 pi,加入其能加入的最短序列,如果不存在就寄了。按照这个方法推出了很奇怪的结论,并不可做。事实上它是假的。

事实上,考虑其证明(肯定是调整法):设 pi 可以加入一个长度为 x,y 的序列且 x<y。如果加入 y 能成立,那么试图构造基于此的加入 x 的方案。一个显然的想法是交换 x 序列的位置 x+1y 序列的位置 y+1,但是交换完后 y 序列的下标不一定递增,这种方法就萎掉了。

正确的贪心是维护 n 个满的序列,从后往前考虑,删除其能作为末尾的最短序列的末尾,不存在就寄。这是可以证明的:考虑把 y 序列的位置 y 放到 x 序列的位置 x。归纳地假设删完这一步后按照该贪心策略有解,那么存在方案使 x 序列的位置 x 下标小于 y 序列的位置 x,那么就可以把整个 x 序列与 y 序列的前 x1 个元素交换,y 序列后面的顺延。

从前往后的一个反例是 p=[1,1,3,1,1,1],n=2,k=3。感性地理解是,从前往后贪心虽然预先选择尽可能小(也就是限制尽可能紧)的机会,但可能让之后的元素没有及时达到大的机会;而从后往前就没有这个问题。

如果按这个贪心的过程 DP,需要记录当前所有序列长度的可重集的信息,这至少是指数级的,寄。

考虑分析这个贪心的过程。设 ci 表示当前长度为 i 的序列个数(这显然能描述该可重集)。那么加入 x,会令可重集中的哪个数自减呢?是最小的 yx 满足 cy>0。这个 lower_bound 的事情就很影响我们处理。

cx 非零的 x 的上一个 cy 非零的 yprvx。那么 (prvx,x] 这一段的影响是一样的。还有什么东西使得 (prvx,x] 这一段一样?后缀和。我们对 c 的后缀和数组 C 进行考察:加入 x,令 Cy=Cx 的最后一个 ycy 自减、cy1 自加——这对应到 C 上,就相当于 Cy 自减。而 C 始终是单调减的,所以我们可以只记录其可重集,那么Cy 自减就和令 Cx 自减等效

这下就好办了,可以把贪心策略转化成相对简单的过程:维护一个序列 p,表示后缀和数组 C 的可重集按照执行过程的排列。那么每次加入 x 就是 --p[x],如果此时 px 显然就寄了。p 初始为 kn,最后要变成 k0。这样一来,总方案数就很好算:就是个可重集排列数,为 (nk)!(n!)k

考虑 anspos,val。接下来的过程就较为 trivial 了。显然考虑枚举从右往左执行完位置 pos+1 时的 p——任意满足 pi=posp。对一个 ppos 前后及其自身三部分显然是割裂开的。[pos+1,nk] 这一段的方案数显然是 (nkpos)!(npi)!pos 处的方案是确定的,就是令 Pval 自减(Pp 从大到小排序后的结果),那么 [1,pos1] 这一段就是 (pos1)!(Pi[i=val])!

还是枚举 P 比较好。一个 P 的答案就是 k!(nkpos)!(pos1)!Pvalcnti!×(nPi)!Pi!,其中 cntiPj=ij 的个数。接下来就是喜闻乐见的背包 DP 时间。对一个总和固定为 s 的长度为 m 的递增序列(设其值域为 v)DP 的方法主要有两种(这题中 s=n2,m=k,v=n):

  1. dpi,sum,las,一个一个元素考虑。转移需要枚举 [las,v],复杂度 O(msv2),在本题中为 O(n4k)
  2. dpi,sum,cnt,一个一个值考虑。转移需要枚举当前 i 的数量,复杂度为 O(smi=1vmin(m,si))。当 s=v 时可以被分析成 O(s2mlogs),然而这题不行,就是 O(vsm2),即 O(n3k2)。当然,这题转移时需要知道 cnt,所以只能采用方法 2,笑死(

在本题中显然可以枚举 val 做一遍 DP,就能求出所有 pos 的答案,毕竟 possum。复杂度就是 O(n3k3),可以通过 n,k30 的数据(用上快模)。

code
constexpr int N = 35, NN = 1010;

typedef unsigned long long ULL;
typedef __uint128_t LLL;
struct FastMod {
ULL b, m;
void init(ULL b){
this->b = b; m = ULL((LLL(1) << 64) / b);
}
ULL operator()(ULL a){
ULL q = (ULL)((LLL(m) *
a) >> 64);
ULL r = a - q *
b;
return r >= b ? r - b : r;
}
} M;
int mul(int x, int y){return M((ULL)x *
y);}

int iv[NN], fc[NN], ifc[NN];

int n, k;
int ans[NN][N];

void mian() {
    n = read(), k = read(), P = read();
    M.init(P);
    iv[1] = 1; REP(i, 2, NN - 1) iv[i] = (ll)iv[P % i] * (P - P / i) % P;
    fc[0] = ifc[0] = 1; REP(i, 1, NN - 1) fc[i] = (ll)fc[i - 1] * i % P, ifc[i] = (ll)ifc[i - 1] * iv[i] % P;
    REP(val, 1, k) {
        static int dp[N][NN][N];
        memset(dp, 0, sizeof(dp));
        dp[0][0][0] = 1;
        REP(i, 0, n) {
            auto f = dp[i], g = dp[i + 1];
            REP(sum, 0, n * k) REP(cnt, 0, k) if(f[sum][cnt]) {
                int base = mul(ifc[n - i], ifc[i]), prod = 1;
                REP(c, 0, k - cnt) {
                    if(sum + i * c > n * k) break;
                    int p = mul(prod, ifc[c]);
                    if(cnt < k - val + 1 && k - val + 1 <= cnt + c) p = mul(p, i);
                    addto(g[sum + i * c][cnt + c], mul(p, f[sum][cnt]));
                    prod = mul(prod, base);
                }
            }
        }
        REP(pos, 1, n * k) {
            int sum = dp[n + 1][pos][k];
            ans[pos][val] = (ll)fc[k] * fc[n * k - pos] % P * fc[pos - 1] % P * sum % P;
        }
    }
    REP(pos, 1, n * k) REP(val, 1, k) prt(ans[pos][val]), pc(" \n"[val == k]);
}
posted @   ycx060617  阅读(227)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
历史上的今天:
2020-04-22 CodeForces 274D - Lovely Matrix
点击右上角即可分享
微信分享提示